diff --git a/.travis.yml b/.travis.yml index 72a9736..33ddb13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ jdk: oraclejdk8 env: global: - - ANDROID_API=27 - - ANDROID_BUILD_TOOLS=27.0.3 + - ANDROID_API=28 + - ANDROID_BUILD_TOOLS=28.0.3 - EMULATOR_API=24 android: @@ -22,7 +22,7 @@ android: - '.+' before_install: - - yes | sdkmanager "platforms;android-27" + - yes | sdkmanager "platforms;android-28" before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock diff --git a/README.md b/README.md index b62226f..3155372 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

Owner & Author: Javier Santos
- Co-Author: Jahir Fiquitiva + Co-Author: Jahir Fiquitiva

## Disclaimer @@ -24,7 +24,7 @@ Some of the techniques included in this library can be found [here](https://www. ## How to include Add the repository to your project **build.gradle**: -```Gradle +```gradle repositories { maven { url "https://jitpack.io" @@ -34,7 +34,7 @@ repositories { And add the library to your module **build.gradle**: -```Gradle +```gradle dependencies { implementation 'com.github.javiersantos:PiracyChecker:1.2.3' } @@ -54,51 +54,102 @@ Google Play offers a licensing service that lets you enforce licensing policies Any application that you publish through Google Play can use the Google Play Licensing service. No special account or registration is needed. For more information check out the [Google Developers page](https://developer.android.com/google/play/licensing/index.html). - -```Java + +```kotlin +piracyChecker { + enableGooglePlayLicensing("BASE_64_LICENSE_KEY") + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableGooglePlayLicensing("BASE_64_LICENSE_KEY") ... .start(); ``` +

+ + + In order to retrieve your BASE64 license key your app must be uploaded to the [Google Play Developer Console](https://play.google.com/apps/publish/). Then access to your app -> Services and APIs. -When using Google Play Licensing your should call `.destroy()` in the `onDestroy()` method of your Activity to avoid multiple instances of the service running. Have a look to the Wiki for a [sample Activity](https://github.com/javiersantos/PiracyChecker/wiki/Using-Google-Play-Licensing-(LVL)) with `.destroy()`. +When using Google Play Licensing your should call `.destroy()` in the `onDestroy()` method of your Activity to avoid multiple instances of the service running. Have a look to the Wiki for a [sample Activity](https://github.com/javiersantos/PiracyChecker/wiki/Using-Google-Play-Licensing-(LVL)) with `destroy()`. ### Verify your app's signing certificate (signature) In a nutshell, developers must sign applications with their private key/certificate (contained in a .keystore file) before the app can be installed on user devices. The signing certificate must stay consistent throughout the life of the app, and typically have an expiry date of 25 years in the future. The app signature will be broken if the .apk is altered in any way — unsigned apps cannot typically be installed. We can imagine an attacker removing license-checking code to enable full app features without paying, for instance. A more dangerous example would be altering the .apk to include malware in a legitimate app to harvest sensitive user data. In order for the altered .apk to be installed, the attacker must resign it. -```Java + +```kotlin +piracyChecker { + enableSigningCertificate("478yYkKAQF+KST8y4ATKvHkYibo=") // The original APK signature for the PRODUCTION version + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableSigningCertificate("478yYkKAQF+KST8y4ATKvHkYibo=") // The original APK signature for the PRODUCTION version ... .start(); ``` +

+ + + **Don't use this method when using [Google Play App Signing](https://support.google.com/googleplay/android-developer/answer/7384423?hl=en) since Google removes the original signature and add another one, so this method will fail.** -**BE CAREFUL!!** Your app signature can be retrieved using a PiracyCheckerUtils method. Make sure that you have signed your APK using your PRODUCTION keystore (not using the DEBUG one) and installed the version that you plan to distribute. Then copy the signature returned by this method on the console and paste in `.enableSigningCertificate("YOUR_APK_SIGNATURE")` +**BE CAREFUL!!** Your app signature can be retrieved using a PiracyCheckerUtils method. Make sure that you have signed your APK using your PRODUCTION keystore (not using the DEBUG one) and installed the version that you plan to distribute. Then copy the signature returned by this method on the console and paste in `enableSigningCertificate("YOUR_APK_SIGNATURE")` -```Java +```kotlin +// This method will print your app signature in the console +Log.e("SIGNATURE", apkSignature) +``` + +
Java Sample + +```java // This method will print your app signature in the console -Log.e("SIGNATURE", PiracyCheckerUtils.getAPKSignature(this)); +Log.e("SIGNATURE", LibraryUtilsKt.getApkSignature(this)); ``` +

+ + + ### Verify the installer If you only plan to distribute the app on a particular store this technique will block from installing the app using any another store. Supported stores: Google Play, Amazon App Store and Samsung Galaxy Apps. -```Java +```kotlin +piracyChecker { + enableInstallerId(InstallerID.GOOGLE_PLAY, InstallerID.AMAZON_APP_STORE, InstallerID.GALAXY_APPS) + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableInstallerId(InstallerID.GOOGLE_PLAY, InstallerID.AMAZON_APP_STORE, InstallerID.GALAXY_APPS) ... .start(); ``` +

+ + + **BE CAREFUL!!** This is a really restrictive technique since it will block your app from being installed using another market or directly installing the .apk on the device. It isn't recommended for most cases. ### Verify the use of pirate apps @@ -106,16 +157,40 @@ If you want to check if user has pirate apps installed, you can use this code. It will check for: Lucky Patcher, Uret Patcher, Freedom, CreeHack and HappyMod. -```Java +```kotlin +piracyChecker { + enableUnauthorizedAppsCheck(true) + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableUnauthorizedAppsCheck(true) ... .start(); ``` +

+ + + #### Add custom apps to check Since version 1.2.2 you can add additional apps to be checked using this code: -```Java + +```kotlin +val app = PirateApp("Lucky Patcher", "the.package.name") +piracyChecker { + addAppToCheck(app) + ... +}.start() +``` + +
Java Sample + +```java PirateApp app = new PirateApp("Lucky Patcher", "the.package.name"); new PiracyChecker(this) .addAppToCheck(app) @@ -123,13 +198,27 @@ new PiracyChecker(this) .start(); ``` +

+ + + You can block the app even when this pirate apps has been uninstalled. This prevents the app from being patched and then uninstall the pirate app in order to continue using your app. The library will save a `SharedPreference` value to know when a pirate app has been detected. There are two ways to do this: Define the `SharedPreferences` and the name of the preference where you want to save the result. -```Java +```kotlin +piracyChecker { + enableUnauthorizedAppsCheck() + blockIfUnauthorizedAppUninstalled(preferences, "app_unauthorized") // Change "app_unauthorized" with your own value + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableUnauthorizedAppsCheck(true) .blockIfUnauthorizedAppUninstalled(preferences, "app_unauthorized") // Change "app_unauthorized" with your own value @@ -137,9 +226,23 @@ new PiracyChecker(this) .start(); ``` +

+ + + Define the `SharedPreferences` name and the name of the preference where you want to save the result. -```Java +```kotlin +piracyChecker { + enableUnauthorizedAppsCheck() + blockIfUnauthorizedAppUninstalled("license_preferences", "app_unauthorized") // Change "license_preferences" and "app_unauthorized" with your own value + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableUnauthorizedAppsCheck(true) .blockIfUnauthorizedAppUninstalled("license_preferences", "app_unauthorized") // Change "license_preferences" and "app_unauthorized" with your own value @@ -147,51 +250,120 @@ new PiracyChecker(this) .start(); ``` +

+ + + ### Verify the use of third-party store apps If you want to check if user has third-party store apps installed, you can use this code. It will check for: Aptoide, BlackMart, Mobogenie, 1Mobile, GetApk, GetJar, SlideMe and ACMarket. -```Java +```kotlin +piracyChecker { + enableStoresCheck() + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableStoresCheck(true) ... .start(); ``` +

+ + + ### Enable deep pirate and third-party store apps check If you want to check if these kind of apps left some files that could make your app work as pirated still, you can enable its check as follows: -```Java + +```kotlin +piracyChecker { + enableFoldersCheck() + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableFoldersCheck(true) ... .start(); ``` +

+ + + If you also want to check for `.apk` files in certain system folders, you can enable it like so: -```Java + +```kotlin +piracyChecker { + enableAPKCheck() + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableAPKCheck(true) ... .start(); ``` +

+ + + **BE CAREFUL!** This means, that some times, the app will be recognized as pirated even after those pirate and third-party store apps were uninstalled. Set it to false if you don't like this behaviour/approach. ### Verify if app is a debug build Allowing apps to be debugged when installed on an Android device is something that, as developers, we only enable during the development process. Therefore, if debugging occurs on a live build of your app, it's likely that someone other than you is trying to analyze the app. -```Java + +```kotlin +piracyChecker { + enableDebugCheck() + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableDebugCheck(true) ... .start(); ``` +

+ + + ### Verify if app is being run in an emulator If your app is running on an emulator outside the development process, it gives an indication that someone other than you is trying to analyze the app. -```Java +```kotlin +val deepCheck = fals +piracyChecker { + .enableEmulatorCheck(deepCheck) + ... +}.start() +``` + +
Java Sample + +```java boolean deep = false; new PiracyChecker(this) .enableEmulatorCheck(deep) @@ -199,6 +371,10 @@ new PiracyChecker(this) .start(); ``` +

+ + + **Note:** the deep boolean with make the library do extra checks to detect if device is an emulator or not. It could lead to some weird crashes, so be wise when using it. ### Save the result of the license check in `SharedPreferences` @@ -209,22 +385,48 @@ There are two ways to do this: Define the `SharedPreferences` and the name of the preference where you want to save the result. -```Java +```kotlin +piracyChecker { + saveResultToSharedPreferences(preferences, "valid_license") // Change "valid_license" with your own value + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .saveResultToSharedPreferences(preferences, "valid_license") // Change "valid_license" with your own value ... .start(); ``` +

+ + + Define the `SharedPreferences` name and the name of the preference where you want to save the result. -```Java +```kotlin +piracyChecker { + saveResultToSharedPreferences("license_preferences", "valid_license") // Change "license_preferences" and "valid_license" with your own value + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .saveResultToSharedPreferences("license_preferences", "valid_license") // Change "license_preferences" and "valid_license" with your own value ... .start(); ``` +

+ + + ## Customizations @@ -234,20 +436,35 @@ It's recommended to show a new Activity instead of a Dialog when the license is By default a non-cancelable Dialog will be displayed. -```Java +```kotlin +piracyChecker { + display(Display.ACTIVITY) + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .display(Display.ACTIVITY) ... .start(); ``` +

+ + + By default, the displayed Activity will use the library colors. To apply a custom primary and primary dark color, and to define if the activity should show normal or light status bar, use: -```java -.withActivityColors(R.color.colorPrimary, R.color.colorPrimaryDark, withLightStatusBar) + +```kotlin +withActivityColors(R.color.colorPrimary, R.color.colorPrimaryDark, withLightStatusBar) ``` You can also define a custom layout xml for this activity content, using: -```java + +```kotlin .withActivityLayout(R.layout.my_custom_layout) ``` @@ -258,6 +475,30 @@ By default, the library will display a non-cancelable dialog if the user is not Use the builder and add following: +```kotlin +callback { + allow { + // Do something when the user is allowed to use the app + } + doNotAllow { piracyCheckerError, pirateApp -> + // You can either do something specific when the user is not allowed to use the app + // Or manage the error, using the 'error' parameter, yourself (Check errors at {@link PiracyCheckerError}). + + // Additionally, if you enabled the check of pirate apps and/or third-party stores, the 'app' param + // is the app that has been detected on device. App can be null, and when null, it means no pirate app or store was found, + // or you disabled the check for those apps. + // This allows you to let users know the possible reasons why license is been invalid. + } + onError { error -> + // This method is not required to be implemented/overriden but... + // You can either do something specific when an error occurs while checking the license, + // Or manage the error, using the 'error' parameter, yourself (Check errors at {@link PiracyCheckerError}). + } +} +``` + +
Java Sample + ```Java .callback(new PiracyCheckerCallback() { @Override @@ -285,20 +526,40 @@ Use the builder and add following: }) ``` +

+ + + ## FAQs #### Can I protect my app using more than one validation method? Sure. You can use as many validation methods in the builder as you want. For example: -```Java +```kotlin +piracyChecker { + enableGooglePlayLicensing("BASE_64_LICENSE_KEY") + enableSigningCertificate("YOUR_APK_SIGNATURE") + enableUnauthorizedAppsCheck() + saveResultToSharedPreferences("my_app_preferences", "valid_license") + ... +}.start() +``` + +
Java Sample + +```java new PiracyChecker(this) .enableGooglePlayLicensing("BASE_64_LICENSE_KEY") .enableSigningCertificate("YOUR_APK_SIGNATURE") .enableUnauthorizedAppsCheck(true) - .saveResultToSharedPreferences("my_app_preferences", "valid_license"); + .saveResultToSharedPreferences("my_app_preferences", "valid_license") ... .start(); ``` +

+ + + ## License Copyright 2018 Javier Santos diff --git a/app/build.gradle b/app/build.gradle index 14d9f6d..8f488f4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" + compileSdkVersion 28 + buildToolsVersion "28.0.3" defaultConfig { applicationId "com.github.javiersantos.piracychecker.demo" minSdkVersion 14 - targetSdkVersion 27 + targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -36,8 +36,9 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:design:27.1.1' - implementation 'com.android.support:cardview-v7:27.1.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support:cardview-v7:28.0.0' implementation project(':library') // Testing-only dependencies @@ -48,4 +49,4 @@ dependencies { androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', { exclude group: 'com.android.support', module: 'support-annotations' }) -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/javiersantos/piracychecker/PiracyCheckerTest.java b/app/src/androidTest/java/com/github/javiersantos/piracychecker/PiracyCheckerTest.java index a279fa5..fce9782 100644 --- a/app/src/androidTest/java/com/github/javiersantos/piracychecker/PiracyCheckerTest.java +++ b/app/src/androidTest/java/com/github/javiersantos/piracychecker/PiracyCheckerTest.java @@ -1,17 +1,17 @@ package com.github.javiersantos.piracychecker; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.test.InstrumentationRegistry; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; +import com.github.javiersantos.piracychecker.callbacks.PiracyCheckerCallback; import com.github.javiersantos.piracychecker.demo.MainActivity; import com.github.javiersantos.piracychecker.enums.InstallerID; -import com.github.javiersantos.piracychecker.enums.PiracyCheckerCallback; import com.github.javiersantos.piracychecker.enums.PiracyCheckerError; import com.github.javiersantos.piracychecker.enums.PirateApp; +import com.github.javiersantos.piracychecker.utils.LibraryUtilsKt; +import org.jetbrains.annotations.NotNull; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -19,7 +19,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; /** * 1. Global test cases. Doesn't require any additional step to run. @@ -28,7 +28,8 @@ public class PiracyCheckerTest { @Rule - public final ActivityTestRule uiThreadTestRule = new ActivityTestRule<>(MainActivity.class); + public final ActivityTestRule uiThreadTestRule = + new ActivityTestRule<>(MainActivity.class); @Test public void verifySigningCertificate_ALLOW() throws Throwable { @@ -37,21 +38,26 @@ public void verifySigningCertificate_ALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableSigningCertificate("VHZs2aiTBiap/F+AYhYeppy0aF0=") - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker OK", true); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - assertTrue(error.toString() + " - Current signature: " + LibraryUtils.getCurrentSignature(InstrumentationRegistry.getTargetContext()), false); - signal.countDown(); - } - }) - .start(); + .enableSigningCertificate("VHZs2aiTBiap/F+AYhYeppy0aF0=") + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker OK", true); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + assertTrue(error.toString() + " - Current signature: " + + LibraryUtilsKt + .getCurrentSignature( + InstrumentationRegistry.getTargetContext()), + false); + signal.countDown(); + } + }) + .start(); } }); @@ -65,24 +71,27 @@ public void verifySigningCertificate_DONTALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableSigningCertificate("478yYkKAQF+KST8y4ATKvHkYibo=") - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker FAILED: The signing certificate is invalid.", false); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (error == PiracyCheckerError.SIGNATURE_NOT_VALID) - assertTrue("PiracyChecker OK", true); - else - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableSigningCertificate("478yYkKAQF+KST8y4ATKvHkYibo=") + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker FAILED: The signing certificate is invalid.", + false); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + if (error == PiracyCheckerError.SIGNATURE_NOT_VALID) + assertTrue("PiracyChecker OK", true); + else + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -96,26 +105,31 @@ public void verifyInstaller_DONTALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableInstallerId(InstallerID.GOOGLE_PLAY) - .enableInstallerId(InstallerID.AMAZON_APP_STORE) - .enableInstallerId(InstallerID.GALAXY_APPS) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker FAILED: The app has been installed using another store.", false); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (error == PiracyCheckerError.INVALID_INSTALLER_ID) - assertTrue("PiracyChecker OK", true); - else - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableInstallerId(InstallerID.GOOGLE_PLAY) + .enableInstallerId(InstallerID.AMAZON_APP_STORE) + .enableInstallerId(InstallerID.GALAXY_APPS) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue( + "PiracyChecker FAILED: The app has been installed using another " + + "store.", + false); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + if (error == PiracyCheckerError.INVALID_INSTALLER_ID) + assertTrue("PiracyChecker OK", true); + else + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -129,21 +143,22 @@ public void verifyUnauthorizedApps_ALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableUnauthorizedAppsCheck(true) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker OK", true); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - assertTrue(error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableUnauthorizedAppsCheck(true) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker OK", true); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + assertTrue(error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -157,21 +172,22 @@ public void verifyThirdPartyStores_ALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableUnauthorizedAppsCheck(true) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker OK", true); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - assertTrue(error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableUnauthorizedAppsCheck(true) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker OK", true); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + assertTrue(error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -185,21 +201,22 @@ public void verifyDeepPirate_ALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableFoldersCheck(true) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker OK", true); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - assertTrue(error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableFoldersCheck(true) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker OK", true); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + assertTrue(error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -213,24 +230,27 @@ public void verifyDebug_DONTALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableDebugCheck(true) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker FAILED: Tests are running on a debug build.", false); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (error == PiracyCheckerError.USING_DEBUG_APP) - assertTrue("PiracyChecker OK", true); - else - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableDebugCheck(true) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker FAILED: Tests are running on a debug build.", + false); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + if (error == PiracyCheckerError.USING_DEBUG_APP) + assertTrue("PiracyChecker OK", true); + else + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -244,24 +264,27 @@ public void verifyEmulator_DONTALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableEmulatorCheck(false) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker FAILED: Tests are running on an emulator.", false); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (error == PiracyCheckerError.USING_APP_IN_EMULATOR) - assertTrue("PiracyChecker OK", true); - else - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableEmulatorCheck(false) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker FAILED: Tests are running on an emulator.", + false); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + if (error == PiracyCheckerError.USING_APP_IN_EMULATOR) + assertTrue("PiracyChecker OK", true); + else + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -275,28 +298,30 @@ public void verifyEmulatorDeep_DONTALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableEmulatorCheck(true) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker FAILED: Tests are running on an emulator.", false); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (error == PiracyCheckerError.USING_APP_IN_EMULATOR) - assertTrue("PiracyChecker OK", true); - else - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableEmulatorCheck(true) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker FAILED: Tests are running on an emulator.", + false); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + if (error == PiracyCheckerError.USING_APP_IN_EMULATOR) + assertTrue("PiracyChecker OK", true); + else + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); signal.await(30, TimeUnit.SECONDS); } - -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/javiersantos/piracychecker/UnauthorizedAppTest.java b/app/src/androidTest/java/com/github/javiersantos/piracychecker/UnauthorizedAppTest.java index 527665b..ce5a005 100644 --- a/app/src/androidTest/java/com/github/javiersantos/piracychecker/UnauthorizedAppTest.java +++ b/app/src/androidTest/java/com/github/javiersantos/piracychecker/UnauthorizedAppTest.java @@ -1,16 +1,15 @@ package com.github.javiersantos.piracychecker; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.test.InstrumentationRegistry; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; +import com.github.javiersantos.piracychecker.callbacks.PiracyCheckerCallback; import com.github.javiersantos.piracychecker.demo.MainActivity; -import com.github.javiersantos.piracychecker.enums.PiracyCheckerCallback; import com.github.javiersantos.piracychecker.enums.PiracyCheckerError; import com.github.javiersantos.piracychecker.enums.PirateApp; +import org.jetbrains.annotations.NotNull; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -21,13 +20,15 @@ import static org.junit.Assert.assertTrue; /** - * 2. Specific test cases for unauthorized apps. Requires to install an unauthorized app before running this tests. + * 2. Specific test cases for unauthorized apps. Requires to install an unauthorized app before + * running this tests. */ @RunWith(AndroidJUnit4.class) public class UnauthorizedAppTest { @Rule - public final ActivityTestRule uiThreadTestRule = new ActivityTestRule<>(MainActivity.class); + public final ActivityTestRule uiThreadTestRule = + new ActivityTestRule<>(MainActivity.class); @Test public void verifyUnauthorizedCustomApp_ALLOW() throws Throwable { @@ -36,21 +37,25 @@ public void verifyUnauthorizedCustomApp_ALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .addAppToCheck(new PirateApp("Demo", uiThreadTestRule.getActivity().getPackageName() + ".other")) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker OK", true); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + .addAppToCheck(new PirateApp("Demo", + uiThreadTestRule.getActivity().getPackageName() + + ".other")) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker OK", true); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -64,24 +69,30 @@ public void verifyUnauthorizedCustomApp_DONTALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .addAppToCheck(new PirateApp("Demo", uiThreadTestRule.getActivity().getPackageName())) - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker FAILED: There is a custom unauthorized app installed.", false); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (error == PiracyCheckerError.PIRATE_APP_INSTALLED) - assertTrue("PiracyChecker OK", true); - else - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + .addAppToCheck( + new PirateApp("Demo", uiThreadTestRule.getActivity().getPackageName())) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue( + "PiracyChecker FAILED: There is a custom unauthorized app " + + "installed.", + false); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + if (error == PiracyCheckerError.PIRATE_APP_INSTALLED) + assertTrue("PiracyChecker OK", true); + else + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -95,25 +106,30 @@ public void verifyUnauthorizedApps_DONTALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableUnauthorizedAppsCheck(true) - .blockIfUnauthorizedAppUninstalled("piracychecker_preferences", "app_unauthorized") - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker FAILED: There is an unauthorized app installed.", false); - signal.countDown(); - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (error == PiracyCheckerError.PIRATE_APP_INSTALLED) - assertTrue("PiracyChecker OK", true); - else - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + .enableUnauthorizedAppsCheck(true) + .blockIfUnauthorizedAppUninstalled("piracychecker_preferences", + "app_unauthorized") + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue( + "PiracyChecker FAILED: There is an unauthorized app installed.", + false); + signal.countDown(); + } + + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + if (error == PiracyCheckerError.PIRATE_APP_INSTALLED) + assertTrue("PiracyChecker OK", true); + else + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); diff --git a/app/src/androidTest/java/com/github/javiersantos/piracychecker/UnauthorizedAppUninstalledTest.java b/app/src/androidTest/java/com/github/javiersantos/piracychecker/UnauthorizedAppUninstalledTest.java index 677378f..266d9c4 100644 --- a/app/src/androidTest/java/com/github/javiersantos/piracychecker/UnauthorizedAppUninstalledTest.java +++ b/app/src/androidTest/java/com/github/javiersantos/piracychecker/UnauthorizedAppUninstalledTest.java @@ -1,16 +1,15 @@ package com.github.javiersantos.piracychecker; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.test.InstrumentationRegistry; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; +import com.github.javiersantos.piracychecker.callbacks.PiracyCheckerCallback; import com.github.javiersantos.piracychecker.demo.MainActivity; -import com.github.javiersantos.piracychecker.enums.PiracyCheckerCallback; import com.github.javiersantos.piracychecker.enums.PiracyCheckerError; import com.github.javiersantos.piracychecker.enums.PirateApp; +import org.jetbrains.annotations.NotNull; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -21,13 +20,15 @@ import static org.junit.Assert.assertTrue; /** - * 3. Specific test cases for unauthorized apps. Requires to uninstall an unauthorized app before running this tests. + * 3. Specific test cases for unauthorized apps. Requires to uninstall an unauthorized app before + * running this tests. */ @RunWith(AndroidJUnit4.class) public class UnauthorizedAppUninstalledTest { @Rule - public final ActivityTestRule uiThreadTestRule = new ActivityTestRule<>(MainActivity.class); + public final ActivityTestRule uiThreadTestRule = + new ActivityTestRule<>(MainActivity.class); @Test public void verifyBlockUnauthorizedApps_DONTALLOW() throws Throwable { @@ -36,25 +37,31 @@ public void verifyBlockUnauthorizedApps_DONTALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableUnauthorizedAppsCheck(true) - .blockIfUnauthorizedAppUninstalled("piracychecker_preferences", "app_unauthorized") - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker FAILED: There was an unauthorized app installed previously.", false); - signal.countDown(); - } + .enableUnauthorizedAppsCheck(true) + .blockIfUnauthorizedAppUninstalled("piracychecker_preferences", + "app_unauthorized") + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue( + "PiracyChecker FAILED: There was an unauthorized app installed " + + "previously.", + false); + signal.countDown(); + } - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (error == PiracyCheckerError.BLOCK_PIRATE_APP) - assertTrue("PiracyChecker OK", true); - else - assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + error.toString(), false); - signal.countDown(); - } - }) - .start(); + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + if (error == PiracyCheckerError.BLOCK_PIRATE_APP) + assertTrue("PiracyChecker OK", true); + else + assertTrue("PiracyChecker FAILED : PiracyCheckError is not " + + error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); @@ -68,25 +75,24 @@ public void verifyUnauthorizedApps_ALLOW() throws Throwable { @Override public void run() { new PiracyChecker(InstrumentationRegistry.getTargetContext()) - .enableUnauthorizedAppsCheck() - .callback(new PiracyCheckerCallback() { - @Override - public void allow() { - assertTrue("PiracyChecker OK", true); - signal.countDown(); - } + .enableUnauthorizedAppsCheck(true) + .callback(new PiracyCheckerCallback() { + @Override + public void allow() { + assertTrue("PiracyChecker OK", true); + signal.countDown(); + } - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - assertTrue(error.toString(), false); - signal.countDown(); - } - }) - .start(); + @Override + public void doNotAllow(@NotNull PiracyCheckerError error, + @org.jetbrains.annotations.Nullable PirateApp app) { + assertTrue(error.toString(), false); + signal.countDown(); + } + }) + .start(); } }); - signal.await(30, TimeUnit.SECONDS); } - -} +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b428622..68437ec 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,22 +4,33 @@ package="com.github.javiersantos.piracychecker.demo"> + + + android:theme="@style/AppTheme.NoActionBar"> + + + + + + + android:name=".MainActivity" + android:label="@string/app_name" + android:theme="@style/AppTheme.NoActionBar"> - + - + diff --git a/app/src/main/java/com/github/javiersantos/piracychecker/demo/KotlinActivity.kt b/app/src/main/java/com/github/javiersantos/piracychecker/demo/KotlinActivity.kt new file mode 100644 index 0000000..2fe5458 --- /dev/null +++ b/app/src/main/java/com/github/javiersantos/piracychecker/demo/KotlinActivity.kt @@ -0,0 +1,121 @@ +package com.github.javiersantos.piracychecker.demo + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.support.v7.app.AlertDialog +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.Toolbar +import android.util.Log +import android.widget.RadioGroup +import com.github.javiersantos.piracychecker.allow +import com.github.javiersantos.piracychecker.callback +import com.github.javiersantos.piracychecker.doNotAllow +import com.github.javiersantos.piracychecker.enums.Display +import com.github.javiersantos.piracychecker.enums.InstallerID +import com.github.javiersantos.piracychecker.onError +import com.github.javiersantos.piracychecker.piracyChecker +import com.github.javiersantos.piracychecker.utils.apkSignature + +@Suppress("unused") +class KotlinActivity : AppCompatActivity() { + private var piracyCheckerDisplay = Display.DIALOG + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val toolbar = findViewById(R.id.toolbar) + val radioDisplay = findViewById(R.id.radio_display) + + setSupportActionBar(toolbar) + + radioDisplay.setOnCheckedChangeListener { _, i -> + when (i) { + R.id.radio_dialog -> piracyCheckerDisplay = Display.DIALOG + R.id.radio_activity -> piracyCheckerDisplay = Display.ACTIVITY + } + } + + // Show APK signature + Log.e("Signature", apkSignature) + } + + fun toGithub() { + startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse("https://github.com/javiersantos/piracyChecker"))) + } + + fun verifySignature() { + piracyChecker { + display(piracyCheckerDisplay) + enableSigningCertificate("478yYkKAQF+KST8y4ATKvHkYibo=") // Wrong signature + //enableSigningCertificate("VHZs2aiTBiap/F+AYhYeppy0aF0=") // Right signature + }.start() + } + + fun readSignature() { + Log.e("Signature", apkSignature) + AlertDialog.Builder(this) + .setTitle("APK") + .setMessage(apkSignature) + .show() + } + + fun verifyInstallerId() { + piracyChecker { + display(piracyCheckerDisplay) + enableInstallerId(InstallerID.GOOGLE_PLAY) + }.start() + } + + fun verifyUnauthorizedApps() { + piracyChecker { + display(piracyCheckerDisplay) + enableUnauthorizedAppsCheck() + //blockIfUnauthorizedAppUninstalled("license_checker", "block") + }.start() + } + + fun verifyStores() { + piracyChecker { + display(piracyCheckerDisplay) + enableStoresCheck() + }.start() + } + + fun verifyDebug() { + piracyChecker { + display(piracyCheckerDisplay) + enableDebugCheck() + callback { + allow { + // Do something when the user is allowed to use the app + } + doNotAllow { piracyCheckerError, pirateApp -> + // You can either do something specific when the user is not allowed to use the app + // Or manage the error, using the 'error' parameter, yourself (Check errors at {@link PiracyCheckerError}). + + // Additionally, if you enabled the check of pirate apps and/or third-party stores, the 'app' param + // is the app that has been detected on device. App can be null, and when null, it means no pirate app or store was found, + // or you disabled the check for those apps. + // This allows you to let users know the possible reasons why license is been invalid. + } + onError { error -> + // This method is not required to be implemented/overriden but... + // You can either do something specific when an error occurs while checking the license, + // Or manage the error, using the 'error' parameter, yourself (Check errors at {@link PiracyCheckerError}). + } + } + }.start() + } + + fun verifyEmulator() { + piracyChecker { + display(piracyCheckerDisplay) + enableEmulatorCheck() + }.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/javiersantos/piracychecker/demo/MainActivity.java b/app/src/main/java/com/github/javiersantos/piracychecker/demo/MainActivity.java index 5b76434..d90b1d4 100644 --- a/app/src/main/java/com/github/javiersantos/piracychecker/demo/MainActivity.java +++ b/app/src/main/java/com/github/javiersantos/piracychecker/demo/MainActivity.java @@ -12,9 +12,9 @@ import android.widget.RadioGroup; import com.github.javiersantos.piracychecker.PiracyChecker; -import com.github.javiersantos.piracychecker.PiracyCheckerUtils; import com.github.javiersantos.piracychecker.enums.Display; import com.github.javiersantos.piracychecker.enums.InstallerID; +import com.github.javiersantos.piracychecker.utils.LibraryUtilsKt; public class MainActivity extends AppCompatActivity { private Display piracyCheckerDisplay = Display.DIALOG; @@ -44,65 +44,63 @@ public void onCheckedChanged(RadioGroup radioGroup, @IdRes int i) { }); // Show APK signature - Log.e("Signature", PiracyCheckerUtils.getAPKSignature(this)); - + Log.e("Signature", LibraryUtilsKt.getApkSignature(this)); } public void toGithub(View view) { startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("https://github.com/javiersantos/PiracyChecker"))); + Uri.parse("https://github.com/javiersantos/PiracyChecker"))); } public void verifySignature(View view) { new PiracyChecker(this) - .display(piracyCheckerDisplay) - .enableSigningCertificate("478yYkKAQF+KST8y4ATKvHkYibo=") // Wrong signature - //.enableSigningCertificate("VHZs2aiTBiap/F+AYhYeppy0aF0=") // Right signature - .start(); + .display(piracyCheckerDisplay) + .enableSigningCertificate("478yYkKAQF+KST8y4ATKvHkYibo=") // Wrong signature + //.enableSigningCertificate("VHZs2aiTBiap/F+AYhYeppy0aF0=") // Right signature + .start(); } public void readSignature(View view) { - Log.e("Signature", PiracyCheckerUtils.getAPKSignature(this)); + Log.e("Signature", LibraryUtilsKt.getApkSignature(this)); new AlertDialog.Builder(this) - .setTitle("APK") - .setMessage(PiracyCheckerUtils.getAPKSignature(this)) - .show(); + .setTitle("APK") + .setMessage(LibraryUtilsKt.getApkSignature((this))) + .show(); } public void verifyInstallerId(View view) { new PiracyChecker(this) - .display(piracyCheckerDisplay) - .enableInstallerId(InstallerID.GOOGLE_PLAY) - .start(); + .display(piracyCheckerDisplay) + .enableInstallerId(InstallerID.GOOGLE_PLAY) + .start(); } public void verifyUnauthorizedApps(View view) { new PiracyChecker(this) - .display(piracyCheckerDisplay) - .enableUnauthorizedAppsCheck(true) - //.blockIfUnauthorizedAppUninstalled("license_checker", "block") - .start(); + .display(piracyCheckerDisplay) + .enableUnauthorizedAppsCheck(true) + //.blockIfUnauthorizedAppUninstalled("license_checker", "block") + .start(); } public void verifyStores(View view) { new PiracyChecker(this) - .display(piracyCheckerDisplay) - .enableStoresCheck(true) - .start(); + .display(piracyCheckerDisplay) + .enableStoresCheck(true) + .start(); } public void verifyDebug(View view) { new PiracyChecker(this) - .display(piracyCheckerDisplay) - .enableDebugCheck(true) - .start(); + .display(piracyCheckerDisplay) + .enableDebugCheck(true) + .start(); } public void verifyEmulator(View view) { new PiracyChecker(this) - .display(piracyCheckerDisplay) - .enableEmulatorCheck(true) - .start(); + .display(piracyCheckerDisplay) + .enableEmulatorCheck(true) + .start(); } - -} +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 30fef16..3a2705f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.2.71' repositories { google() jcenter() maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:3.1.1' + classpath 'com.android.tools.build:gradle:3.2.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 08e691c..137cbb5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip diff --git a/library/build.gradle b/library/build.gradle index b586a00..218297c 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,14 +1,17 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'com.github.dcendents.android-maven' +group='com.github.javiersantos' android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" + compileSdkVersion 28 + buildToolsVersion "28.0.3" defaultConfig { minSdkVersion 14 - targetSdkVersion 27 - versionCode 123 - versionName "1.2.3" + targetSdkVersion 28 + versionCode 124 + versionName "1.2.4" consumerProguardFiles 'proguard-rules.pro' } buildTypes { @@ -22,5 +25,10 @@ android { dependencies { api fileTree(dir: 'libs', include: ['*.jar']) - api 'com.android.support:appcompat-v7:27.1.1' + api 'com.android.support:appcompat-v7:28.0.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } + +repositories { + mavenCentral() +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/licensing/LibraryChecker.java b/library/src/main/java/com/github/javiersantos/licensing/LibraryChecker.java index 899bf0d..07b0519 100644 --- a/library/src/main/java/com/github/javiersantos/licensing/LibraryChecker.java +++ b/library/src/main/java/com/github/javiersantos/licensing/LibraryChecker.java @@ -83,14 +83,14 @@ public class LibraryChecker implements ServiceConnection { /** * @param context - * a Context + * a Context * @param policy - * implementation of Policy + * implementation of Policy * @param encodedPublicKey - * Base64-encoded RSA public key + * Base64-encoded RSA public key * * @throws IllegalArgumentException - * if encodedPublicKey is invalid + * if encodedPublicKey is invalid */ public LibraryChecker(Context context, Policy policy, String encodedPublicKey) { mContext = context; @@ -107,10 +107,10 @@ public LibraryChecker(Context context, Policy policy, String encodedPublicKey) { * Generates a PublicKey instance from a string containing the Base64-encoded public key. * * @param encodedPublicKey - * Base64-encoded public key + * Base64-encoded public key * * @throws IllegalArgumentException - * if encodedPublicKey is invalid + * if encodedPublicKey is invalid */ private static PublicKey generatePublicKey(String encodedPublicKey) { try { @@ -134,14 +134,14 @@ private static PublicKey generatePublicKey(String encodedPublicKey) { * Get version code for the application package name. * * @param packageName - * application package name + * 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); + context.getPackageManager().getPackageInfo(packageName, 0).versionCode); } catch (NameNotFoundException e) { Log.e(TAG, "Package not found. could not get version code."); return ""; @@ -169,35 +169,35 @@ callback, generateNonce(), Log.i(TAG, "Binding to licensing service."); try { boolean bindResult = mContext - .bindService( - new Intent( - new String( - // Base64 encoded - - // com.android.vending.licensing - // .ILicensingService - // 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=="))), - this, // ServiceConnection. - Context.BIND_AUTO_CREATE); + .bindService( + new Intent( + new String( + // Base64 encoded - + // com.android.vending.licensing + // .ILicensingService + // 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=="))), + this, // ServiceConnection. + Context.BIND_AUTO_CREATE); if (bindResult) { mPendingChecks.offer(validator); } else { @@ -222,8 +222,8 @@ private void runChecks() { try { Log.i(TAG, "Calling checkLicense on service for " + validator.getPackageName()); mService.checkLicense( - validator.getNonce(), validator.getPackageName(), - new ResultListener(validator)); + validator.getNonce(), validator.getPackageName(), + new ResultListener(validator)); mChecksInProgress.add(validator); } catch (RemoteException e) { Log.w(TAG, "RemoteException in checkLicense call.", e); diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/Extensions.kt b/library/src/main/java/com/github/javiersantos/piracychecker/Extensions.kt new file mode 100644 index 0000000..806d603 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/Extensions.kt @@ -0,0 +1,46 @@ +package com.github.javiersantos.piracychecker + +import android.content.Context +import android.support.v4.app.Fragment +import com.github.javiersantos.piracychecker.callbacks.AllowCallback +import com.github.javiersantos.piracychecker.callbacks.DoNotAllowCallback +import com.github.javiersantos.piracychecker.callbacks.OnErrorCallback +import com.github.javiersantos.piracychecker.callbacks.PiracyCheckerCallbacksDSL +import com.github.javiersantos.piracychecker.enums.PiracyCheckerError +import com.github.javiersantos.piracychecker.enums.PirateApp + +fun Context.piracyChecker(builder: PiracyChecker.() -> Unit): PiracyChecker { + val checker = PiracyChecker(this) + checker.builder() + return checker +} + +fun Fragment.piracyChecker(builder: PiracyChecker.() -> Unit): PiracyChecker = + context!!.piracyChecker(builder) + +inline fun PiracyChecker.allow(crossinline allow: () -> Unit = {}) = apply { + allowCallback(object : AllowCallback { + override fun allow() = allow() + }) +} + +inline fun PiracyChecker.doNotAllow(crossinline doNotAllow: (PiracyCheckerError, PirateApp?) -> Unit = { _, _ -> }) = + apply { + doNotAllowCallback(object : DoNotAllowCallback { + override fun doNotAllow(error: PiracyCheckerError, app: PirateApp?) = + doNotAllow(error, app) + }) + } + +inline fun PiracyChecker.onError(crossinline onError: (PiracyCheckerError) -> Unit = {}) = apply { + onErrorCallback(object : OnErrorCallback { + override fun onError(error: PiracyCheckerError) { + super.onError(error) + onError(error) + } + }) +} + +fun PiracyChecker.callback(callbacks: PiracyCheckerCallbacksDSL.() -> Unit) { + PiracyCheckerCallbacksDSL(this).callbacks() +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/LibraryUtils.java b/library/src/main/java/com/github/javiersantos/piracychecker/LibraryUtils.java deleted file mode 100644 index 8ed5ebf..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/LibraryUtils.java +++ /dev/null @@ -1,409 +0,0 @@ -package com.github.javiersantos.piracychecker; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.Signature; -import android.opengl.GLES20; -import android.os.Build; -import android.os.Environment; -import android.support.v4.app.ActivityCompat; -import android.support.v7.app.AlertDialog; -import android.util.Base64; - -import com.github.javiersantos.piracychecker.enums.AppType; -import com.github.javiersantos.piracychecker.enums.InstallerID; -import com.github.javiersantos.piracychecker.enums.PirateApp; - -import java.io.File; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; - -class LibraryUtils { - - static AlertDialog buildUnlicensedDialog(Context context, String title, String content) { - if (!(context instanceof Activity)) return null; - final Activity activity = (Activity) context; - return new AlertDialog.Builder(context) - .setCancelable(false) - .setTitle(title) - .setMessage(content) - .setPositiveButton(context.getString(R.string.app_unlicensed_close), new - DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (activity.isFinishing()) - return; - activity.finish(); - } - }) - .create(); - } - - @SuppressLint("PackageManagerGetSignatures") - static String getCurrentSignature(Context context) { - String res = ""; - try { - PackageInfo packageInfo = context.getPackageManager().getPackageInfo( - context.getPackageName(), PackageManager.GET_SIGNATURES); - for (Signature signature : packageInfo.signatures) { - MessageDigest messageDigest = MessageDigest.getInstance("SHA"); - messageDigest.update(signature.toByteArray()); - res = Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT); - } - } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException ignored) { - } - return res.trim(); - } - - static boolean verifySigningCertificate(Context context, String appSignature) { - return getCurrentSignature(context).equals(appSignature); - } - - static boolean verifyInstallerId(Context context, List installerID) { - List validInstallers = new ArrayList<>(); - final String installer = context.getPackageManager().getInstallerPackageName( - context.getPackageName()); - - for (InstallerID id : installerID) { - validInstallers.addAll(id.toIDs()); - } - - return installer != null && validInstallers.contains(installer); - } - - @SuppressLint("SdCardPath") - static PirateApp getPirateApp(Context context, - boolean lpf, boolean stores, boolean folders, boolean apks, - ArrayList extraApps) { - if (!lpf && !stores && extraApps.isEmpty()) return null; - for (PirateApp app : getApps(extraApps)) { - if ((lpf && app.getType() == AppType.PIRATE) || - (stores && app.getType() == AppType.STORE) || app.getType() == AppType.OTHER) { - String pack = app.getPackage(); - PackageManager pm = context.getPackageManager(); - try { - PackageInfo info = pm.getPackageInfo(pack, PackageManager.GET_META_DATA); - if (info != null) return app; - } catch (PackageManager.NameNotFoundException ignored1) { - try { - Intent intent = pm.getLaunchIntentForPackage(pack); - if (isIntentAvailable(context, intent)) { - return app; - } - } catch (Exception ignored2) { - try { - if (hasPermissions(context)) { - boolean apkExist = false; - if (apks) { - File file1 = new File("/data/app/" + pack + "-1/base.apk"); - File file2 = new File("/data/app/" + pack + "-2/base.apk"); - File file3 = new File("/data/app/" + pack + ".apk"); - File file4 = new File("/data/data/" + pack + ".apk"); - File file5 = - new File(context.getFilesDir().getPath() + pack + - ".apk"); - apkExist = file1.exists() || file2.exists() || file3.exists() || - file4.exists() || file5.exists(); - } - - boolean foldersExist = false; - boolean containsFolder = false; - - if (folders) { - File file6 = new File("/data/data/" + pack); - File file7 = new File(context.getFilesDir().getPath() + pack); - File file8 = - new File(Environment.getExternalStorageDirectory() + - "/Android/data/" + pack); - foldersExist = - file6.exists() || file7.exists() || file8.exists(); - - File appsContainer = new File("/data/app/"); - if (appsContainer.exists()) { - for (File f : appsContainer.listFiles()) { - if (f.getName().startsWith(pack)) - containsFolder = true; - } - } - } - - if (containsFolder || apkExist || foldersExist) { - return app; - } - } - } catch (Exception ignored) { - } - } - } - } - } - return null; - } - - /** - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - *

- * Copyright (C) 2013, Vladislav Gingo Skoumal (http://www.skoumal.net) - */ - static boolean isInEmulator(boolean deepCheck) { - int ratingCheckEmulator = 0; - - if (Build.PRODUCT.contains("sdk") || - Build.PRODUCT.contains("Andy") || - Build.PRODUCT.contains("ttVM_Hdragon") || - Build.PRODUCT.contains("google_sdk") || - Build.PRODUCT.contains("Droid4X") || - Build.PRODUCT.contains("nox") || - Build.PRODUCT.contains("sdk_x86") || - Build.PRODUCT.contains("sdk_google") || - Build.PRODUCT.contains("vbox86p")) { - ratingCheckEmulator++; - } - - if (Build.MANUFACTURER.equals("unknown") || - Build.MANUFACTURER.equals("Genymotion") || - Build.MANUFACTURER.contains("Andy") || - Build.MANUFACTURER.contains("MIT") || - Build.MANUFACTURER.contains("nox") || - Build.MANUFACTURER.contains("TiantianVM")) { - ratingCheckEmulator++; - } - - if (Build.BRAND.equals("generic") || - Build.BRAND.equals("generic_x86") || - Build.BRAND.equals("TTVM") || - Build.BRAND.contains("Andy")) { - ratingCheckEmulator++; - } - - if (Build.DEVICE.contains("generic") || - Build.DEVICE.contains("generic_x86") || - Build.DEVICE.contains("Andy") || - Build.DEVICE.contains("ttVM_Hdragon") || - Build.DEVICE.contains("Droid4X") || - Build.DEVICE.contains("nox") || - Build.DEVICE.contains("generic_x86_64") || - Build.DEVICE.contains("vbox86p")) { - ratingCheckEmulator++; - } - - if (Build.MODEL.equals("sdk") || - Build.MODEL.equals("google_sdk") || - Build.MODEL.contains("Droid4X") || - Build.MODEL.contains("TiantianVM") || - Build.MODEL.contains("Andy") || - Build.MODEL.equals("Android SDK built for x86_64") || - Build.MODEL.equals("Android SDK built for x86")) { - ratingCheckEmulator++; - } - - if (Build.HARDWARE.equals("goldfish") || - Build.HARDWARE.equals("vbox86") || - Build.HARDWARE.contains("nox") || - Build.HARDWARE.contains("ttVM_x86")) { - ratingCheckEmulator++; - } - - if (Build.FINGERPRINT.contains("generic") || - Build.FINGERPRINT.contains("generic/sdk/generic") || - Build.FINGERPRINT.contains("generic_x86/sdk_x86/generic_x86") || - Build.FINGERPRINT.contains("Andy") || - Build.FINGERPRINT.contains("ttVM_Hdragon") || - Build.FINGERPRINT.contains("generic_x86_64") || - Build.FINGERPRINT.contains("generic/google_sdk/generic") || - Build.FINGERPRINT.contains("vbox86p") || - Build.FINGERPRINT.contains("generic/vbox86p/vbox86p")) { - ratingCheckEmulator++; - } - - if (deepCheck) { - try { - String opengl = GLES20.glGetString(GLES20.GL_RENDERER); - if (opengl != null) { - if (opengl.contains("Bluestacks") || opengl.contains("Translator")) - ratingCheckEmulator += 10; - } - } catch (Exception ignored) { - } - - try { - File sharedFolder = new File(Environment.getExternalStorageDirectory().toString() - + File.separatorChar - + "windows" - + File.separatorChar - + "BstSharedFolder"); - if (sharedFolder.exists()) - ratingCheckEmulator += 10; - } catch (Exception ignored) { - } - } - - return ratingCheckEmulator > 3; - } - - static boolean isDebug(Context context) { - return ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); - } - - private static ArrayList getApps(ArrayList extraApps) { - ArrayList apps = new ArrayList<>(); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "c", "h", "e", - "l", "p", "u", "s", ".", "l", "a", "c", - "k", "y", "p", "a", "t", "c", "h"}, - AppType.PIRATE)); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "d", "i", "m", - "o", "n", "v", "i", "d", "e", "o", ".", - "l", "u", "c", "k", "y", "p", "a", "t", - "c", "h", "e", "r"}, - AppType.PIRATE)); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "f", "o", "r", - "p", "d", "a", ".", "l", "p"}, - AppType.PIRATE)); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "a", "n", "d", - "r", "o", "i", "d", ".", "v", "e", "n", - "d", "i", "n", "g", ".", "b", "i", "l", - "l", "i", "n", "g", ".", "I", "n", "A", - "p", "p", "B", "i", "l", "l", "i", "n", - "g", "S", "e", "r", "v", "i", "c", "e", - ".", "L", "U", "C", "K"}, - AppType.PIRATE)); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "a", "n", "d", - "r", "o", "i", "d", ".", "v", "e", "n", - "d", "i", "n", "g", ".", "b", "i", "l", - "l", "i", "n", "g", ".", "I", "n", "A", - "p", "p", "B", "i", "l", "l", "i", "n", - "g", "S", "e", "r", "v", "i", "c", "e", - ".", "L", "O", "C", "K"}, - AppType.PIRATE)); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "a", "n", "d", - "r", "o", "i", "d", ".", "v", "e", "n", - "d", "i", "n", "g", ".", "b", "i", "l", - "l", "i", "n", "g", ".", "I", "n", "A", - "p", "p", "B", "i", "l", "l", "i", "n", - "g", "S", "e", "r", "v", "i", "c", "e", - ".", "L", "A", "C", "K"}, - AppType.PIRATE)); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "a", "n", "d", - "r", "o", "i", "d", ".", "v", "e", "n", - "d", "i", "n", "g", ".", "b", "i", "l", - "l", "i", "n", "g", ".", "I", "n", "A", - "p", "p", "B", "i", "l", "l", "i", "n", - "g", "S", "e", "r", "v", "i", "c", "e", - ".", "C", "L", "O", "N"}, - AppType.PIRATE)); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "a", "n", "d", - "r", "o", "i", "d", ".", "v", "e", "n", - "d", "i", "n", "g", ".", "b", "i", "l", - "l", "i", "n", "g", ".", "I", "n", "A", - "p", "p", "B", "i", "l", "l", "i", "n", - "g", "S", "e", "r", "v", "i", "c", "e", - ".", "C", "R", "A", "C"}, - AppType.PIRATE)); - apps.add(new PirateApp("Lucky Patcher", new String[]{"c", "o", "m", ".", "a", "n", "d", - "r", "o", "i", "d", ".", "v", "e", "n", - "d", "i", "n", "g", ".", "b", "i", "l", - "l", "i", "n", "g", ".", "I", "n", "A", - "p", "p", "B", "i", "l", "l", "i", "n", - "g", "S", "e", "r", "v", "i", "c", "e", - ".", "C", "O", "I", "N"}, - AppType.PIRATE)); - apps.add(new PirateApp("Uret Patcher", new String[]{"u", "r", "e", "t", ".", "j", "a", - "s", "i", "2", "1", "6", "9", ".", "p", - "a", "t", "c", "h", "e", "r"}, - AppType.PIRATE)); - apps.add(new PirateApp("Freedom", new String[]{"c", "c", ".", "m", "a", "d", "k", "i", - "t", "e", ".", "f", "r", "e", "e", "d", "o", - "m"}, - AppType.PIRATE)); - apps.add(new PirateApp("Freedom", new String[]{"c", "c", ".", "c", "z", ".", "m", "a", - "d", "k", "i", "t", "e", ".", "f", "r", "e", - "e", "d", "o", "m"}, - AppType.PIRATE)); - apps.add(new PirateApp("CreeHack", new String[]{"o", "r", "g", ".", "c", "r", "e", "e", - "p", "l", "a", "y", "s", ".", "h", "a", "c", - "k"}, - AppType.PIRATE)); - apps.add(new PirateApp("HappyMod", new String[]{"c", "o", "m", ".", "h", "a", "p", "p", "y", - "m", "o", "d", ".", "a", "p", "k"}, - AppType.PIRATE)); - apps.add(new PirateApp("Aptoide", new String[]{"c", "m", ".", "a", "p", "t", "o", "i", - "d", "e", ".", "p", "t"}, - AppType.STORE)); - apps.add(new PirateApp("BlackMart", new String[]{"o", "r", "g", ".", "b", "l", "a", "c", - "k", "m", "a", "r", "t", ".", "m", "a", - "r", "k", "e", "t"}, - AppType.STORE)); - apps.add(new PirateApp("Mobogenie", new String[]{"c", "o", "m", ".", "m", "o", "b", "o", - "g", "e", "n", "i", "e"}, - AppType.STORE)); - apps.add(new PirateApp("1Mobile", new String[]{"m", "e", ".", "o", "n", "e", "m", "o", - "b", "i", "l", "e", ".", "a", "n", "d", "r", - "o", "i", "d"}, - AppType.STORE)); - apps.add(new PirateApp("GetApk", new String[]{"c", "o", "m", ".", "r", "e", "p", "o", - "d", "r", "o", "i", "d", ".", "a", "p", - "p"}, - AppType.STORE)); - apps.add(new PirateApp("GetJar", new String[]{"c", "o", "m", ".", "g", "e", "t", "j", - "a", "r", ".", "r", "e", "w", "a", "r", "d", - "s"}, - AppType.STORE)); - apps.add(new PirateApp("SlideMe", new String[]{"c", "o", "m", ".", "s", "l", "i", "d", - "e", "m", "e", ".", "s", "a", "m", ".", "m", - "a", "n", "a", "g", "e", "r"}, - AppType.STORE)); - apps.add(new PirateApp("ACMarket", new String[]{"n", "e", "t", ".", "a", "p", "p", "c", - "a", "k", "e"}, - AppType.STORE)); - apps.addAll(extraApps); - return apps; - } - - private static boolean isIntentAvailable(Context ctx, Intent intent) { - final PackageManager mgr = ctx.getPackageManager(); - List list = mgr.queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY); - return list != null && list.size() > 0; - } - - private static boolean hasPermissions(Context context) { - try { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN || - !shouldAskPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) || - (!(ActivityCompat.shouldShowRequestPermissionRationale( - (Activity) context, Manifest.permission.READ_EXTERNAL_STORAGE))); - } catch (Exception e) { - return false; - } - } - - private static boolean shouldAskPermission() { - return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); - } - - private static boolean shouldAskPermission(Context context, String permission) { - if (shouldAskPermission()) { - int permissionResult = ActivityCompat.checkSelfPermission(context, permission); - return permissionResult != PackageManager.PERMISSION_GRANTED; - } - return false; - } -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/PiracyChecker.java b/library/src/main/java/com/github/javiersantos/piracychecker/PiracyChecker.java deleted file mode 100644 index 7ad87ea..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/PiracyChecker.java +++ /dev/null @@ -1,420 +0,0 @@ -package com.github.javiersantos.piracychecker; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.provider.Settings; -import android.support.annotation.ColorRes; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.util.Log; - -import com.github.javiersantos.licensing.AESObfuscator; -import com.github.javiersantos.licensing.LibraryChecker; -import com.github.javiersantos.licensing.LibraryCheckerCallback; -import com.github.javiersantos.licensing.ServerManagedPolicy; -import com.github.javiersantos.piracychecker.activities.LicenseActivity; -import com.github.javiersantos.piracychecker.enums.AppType; -import com.github.javiersantos.piracychecker.enums.Display; -import com.github.javiersantos.piracychecker.enums.InstallerID; -import com.github.javiersantos.piracychecker.enums.PiracyCheckerCallback; -import com.github.javiersantos.piracychecker.enums.PiracyCheckerError; -import com.github.javiersantos.piracychecker.enums.PirateApp; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@SuppressWarnings({"WeakerAccess", "unused"}) -@SuppressLint("HardwareIds") -public class PiracyChecker { - - private static final String LIBRARY_PREFERENCES_NAME = "license_check"; - - // Library configuration/customizations - private Context context; - private String unlicensedDialogTitle; - private String unlicensedDialogDescription; - private Display display; - @ColorRes - private int colorPrimary; - @ColorRes - private int colorPrimaryDark; - private boolean withLightStatusBar; - @LayoutRes - private int layoutXML = -1; - private boolean enableLVL; - private boolean enableSigningCertificate; - private boolean enableUnauthorizedAppsCheck; - private boolean enableStoresCheck; - private boolean enableEmulatorCheck; - private boolean enableDeepEmulatorCheck; - private boolean enableDebugCheck; - private boolean enableFoldersCheck; - private boolean enableAPKCheck; - private boolean saveToSharedPreferences; - private boolean blockUnauthorized; - private SharedPreferences preferences; - private String preferenceSaveResult; - private String preferenceBlockUnauthorized; - private String licenseBase64; - private String signature; - private List installerIDs; - private PiracyCheckerCallback callback; - private ArrayList extraApps; - - // LVL - private LibraryChecker libraryLVLChecker; - // Dialog - private PiracyCheckerDialog dialog; - - public PiracyChecker(Context context) { - this(context, context.getString(R.string.app_unlicensed), - context.getString(R.string.app_unlicensed_description)); - } - - public PiracyChecker(Context context, String title, String description) { - this.context = context; - this.unlicensedDialogTitle = title; - this.unlicensedDialogDescription = description; - this.display = Display.DIALOG; - this.installerIDs = new ArrayList<>(); - this.extraApps = new ArrayList<>(); - this.colorPrimary = R.color.colorPrimary; - this.colorPrimaryDark = R.color.colorPrimaryDark; - } - - public PiracyChecker(Context context, @StringRes int title, @StringRes int description) { - this(context, context.getString(title), context.getString(description)); - } - - public PiracyChecker enableGooglePlayLicensing(String licenseKeyBase64) { - this.enableLVL = true; - this.licenseBase64 = licenseKeyBase64; - return this; - } - - public PiracyChecker enableSigningCertificate(String signature) { - this.enableSigningCertificate = true; - this.signature = signature; - return this; - } - - public PiracyChecker enableInstallerId(InstallerID... installerID) { - this.installerIDs.addAll(Arrays.asList(installerID)); - return this; - } - - @Deprecated - public PiracyChecker enableUnauthorizedAppsCheck() { - this.enableUnauthorizedAppsCheck = true; - return this; - } - - public PiracyChecker enableUnauthorizedAppsCheck(boolean enable) { - this.enableUnauthorizedAppsCheck = enable; - return this; - } - - public PiracyChecker blockIfUnauthorizedAppUninstalled(SharedPreferences preferences, - @NonNull String preferenceName) { - this.blockUnauthorized = true; - this.preferenceBlockUnauthorized = preferenceName; - saveToSharedPreferences(preferences); - return this; - } - - public PiracyChecker blockIfUnauthorizedAppUninstalled(String preferencesName, - @NonNull String preferenceName) { - this.blockUnauthorized = true; - this.preferenceBlockUnauthorized = preferenceName; - saveToSharedPreferences(preferencesName); - return this; - } - - @Deprecated - public PiracyChecker enableStoresCheck() { - this.enableStoresCheck = true; - return this; - } - - @Deprecated - public PiracyChecker enableDebugCheck() { - this.enableDebugCheck = true; - return this; - } - - public PiracyChecker enableStoresCheck(boolean enable) { - this.enableStoresCheck = enable; - return this; - } - - public PiracyChecker enableDebugCheck(boolean enable) { - this.enableDebugCheck = enable; - return this; - } - - public PiracyChecker enableAPKCheck(boolean enable) { - this.enableAPKCheck = enable; - return this; - } - - public PiracyChecker enableEmulatorCheck(boolean deepCheck) { - this.enableEmulatorCheck = true; - this.enableDeepEmulatorCheck = deepCheck; - return this; - } - - public PiracyChecker enableFoldersCheck(boolean foldersCheck) { - this.enableFoldersCheck = foldersCheck; - return this; - } - - public PiracyChecker addAppToCheck(PirateApp... apps) { - this.extraApps.addAll(Arrays.asList(apps)); - return this; - } - - public PiracyChecker saveResultToSharedPreferences(SharedPreferences preferences, - @NonNull String preferenceName) { - this.saveToSharedPreferences = true; - this.preferenceSaveResult = preferenceName; - saveToSharedPreferences(preferences); - return this; - } - - public PiracyChecker saveResultToSharedPreferences(String preferencesName, - @NonNull String preferenceName) { - this.saveToSharedPreferences = true; - this.preferenceSaveResult = preferenceName; - saveToSharedPreferences(preferencesName); - return this; - } - - private void saveToSharedPreferences(SharedPreferences preferences) { - if (preferences != null) { - this.preferences = preferences; - } else { - try { - this.preferences = ((Activity) context).getPreferences(Context.MODE_PRIVATE); - } catch (Exception e) { - this.preferences = context.getSharedPreferences(LIBRARY_PREFERENCES_NAME, - Context.MODE_PRIVATE); - } - } - } - - private void saveToSharedPreferences(String preferencesName) { - if (preferencesName != null) { - this.preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE); - } else { - try { - this.preferences = ((Activity) context).getPreferences(Context.MODE_PRIVATE); - } catch (Exception e) { - this.preferences = context.getSharedPreferences(LIBRARY_PREFERENCES_NAME, - Context.MODE_PRIVATE); - } - } - } - - public PiracyChecker display(Display display) { - this.display = display; - return this; - } - - public PiracyChecker withActivityColors(@ColorRes int colorPrimary, - @ColorRes int colorPrimaryDark, - boolean withLightStatusBar) { - this.colorPrimary = colorPrimary; - this.colorPrimaryDark = colorPrimaryDark; - this.withLightStatusBar = withLightStatusBar; - return this; - } - - public PiracyChecker withActivityLayout(@LayoutRes int layout) { - this.layoutXML = layout; - return this; - } - - public PiracyChecker callback(PiracyCheckerCallback callback) { - this.callback = callback; - return this; - } - - public void destroy() { - dismissDialog(); - destroyLVLChecker(); - context = null; - } - - public void start() { - if (callback == null) { - this.callback = new PiracyCheckerCallback() { - @Override - public void allow() { - } - - @Override - public void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app) { - if (context instanceof Activity && ((Activity) context).isFinishing()) { - return; - } - - String dialogContent = unlicensedDialogDescription; - if (app != null) - dialogContent = context.getString(R.string.unauthorized_app_found, - app.getName()); - else if (error.equals(PiracyCheckerError.BLOCK_PIRATE_APP)) - dialogContent = context.getString(R.string.unauthorized_app_blocked); - - if (display == Display.DIALOG) { - dismissDialog(); - dialog = PiracyCheckerDialog.newInstance(unlicensedDialogTitle, - dialogContent); - if (dialog != null) { - dialog.show(context); - } else { - Log.e("PiracyChecker", "Unlicensed dialog was not built properly. " + - "Make sure your context is an instance of Activity"); - } - } else { - Intent intent = new Intent(context, LicenseActivity.class) - .putExtra("content", dialogContent) - .putExtra("colorPrimary", colorPrimary) - .putExtra("colorPrimaryDark", colorPrimaryDark) - .putExtra("withLightStatusBar", withLightStatusBar) - .putExtra("layoutXML", layoutXML); - context.startActivity(intent); - if (context instanceof Activity) { - ((Activity) context).finish(); - } - destroy(); - } - } - }; - } - verify(callback); - } - - private void verify(final PiracyCheckerCallback verifyCallback) { - // Library will check first the non-LVL methods since LVL is asynchronous and could take - // some seconds to give a result - if (!verifySigningCertificate()) { - verifyCallback.dontAllow(PiracyCheckerError.SIGNATURE_NOT_VALID, null); - } else if (!verifyInstallerId()) { - verifyCallback.dontAllow(PiracyCheckerError.INVALID_INSTALLER_ID, null); - } else if (!verifyUnauthorizedApp()) { - verifyCallback.dontAllow(PiracyCheckerError.BLOCK_PIRATE_APP, null); - } else { - if (enableLVL) { - String deviceId = Settings.Secure.getString(context.getContentResolver(), - Settings.Secure.ANDROID_ID); - destroyLVLChecker(); - libraryLVLChecker = - new LibraryChecker( - context, - new ServerManagedPolicy(context, - new AESObfuscator( - SaltUtils.getSalt(context), - context.getPackageName(), - deviceId)), - licenseBase64); - libraryLVLChecker.checkAccess(new LibraryCheckerCallback() { - @Override - public void allow(int reason) { - doExtraVerification(verifyCallback, true); - } - - @Override - public void dontAllow(int reason) { - doExtraVerification(verifyCallback, false); - } - - @Override - public void applicationError(int errorCode) { - verifyCallback.onError( - PiracyCheckerError.getCheckerErrorFromCode(errorCode)); - } - }); - } else { - doExtraVerification(verifyCallback, true); - } - } - } - - private boolean verifySigningCertificate() { - return !enableSigningCertificate || - LibraryUtils.verifySigningCertificate(context, signature); - } - - private boolean verifyInstallerId() { - return installerIDs.isEmpty() || LibraryUtils.verifyInstallerId(context, installerIDs); - } - - private boolean verifyUnauthorizedApp() { - return !blockUnauthorized || !preferences.getBoolean(preferenceBlockUnauthorized, false); - } - - private void doExtraVerification(PiracyCheckerCallback verifyCallback, - boolean possibleSuccess) { - PirateApp app = LibraryUtils.getPirateApp(context, enableUnauthorizedAppsCheck, - enableStoresCheck, enableFoldersCheck, - enableAPKCheck, extraApps); - if (possibleSuccess) { - if (enableDebugCheck && LibraryUtils.isDebug(context)) { - if (preferences != null && saveToSharedPreferences) - preferences.edit().putBoolean(preferenceSaveResult, false).apply(); - verifyCallback.dontAllow(PiracyCheckerError.USING_DEBUG_APP, null); - } else if (enableEmulatorCheck && LibraryUtils.isInEmulator(enableDeepEmulatorCheck)) { - if (preferences != null && saveToSharedPreferences) - preferences.edit().putBoolean(preferenceSaveResult, false).apply(); - verifyCallback.dontAllow(PiracyCheckerError.USING_APP_IN_EMULATOR, null); - } else if (app != null) { - if (preferences != null && saveToSharedPreferences) - preferences.edit().putBoolean(preferenceSaveResult, false).apply(); - if (preferences != null && blockUnauthorized && app.getType() == AppType.PIRATE) - preferences.edit().putBoolean(preferenceBlockUnauthorized, true).apply(); - verifyCallback.dontAllow(app.getType() == AppType.STORE - ? PiracyCheckerError.THIRD_PARTY_STORE_INSTALLED - : PiracyCheckerError.PIRATE_APP_INSTALLED, app); - } else { - if (preferences != null && saveToSharedPreferences) - preferences.edit().putBoolean(preferenceSaveResult, true).apply(); - verifyCallback.allow(); - } - } else { - if (app != null) { - if (preferences != null && saveToSharedPreferences) - preferences.edit().putBoolean(preferenceSaveResult, false).apply(); - if (preferences != null && blockUnauthorized && app.getType() == AppType.PIRATE) - preferences.edit().putBoolean(preferenceBlockUnauthorized, true).apply(); - verifyCallback.dontAllow(app.getType() == AppType.STORE - ? PiracyCheckerError.THIRD_PARTY_STORE_INSTALLED - : PiracyCheckerError.PIRATE_APP_INSTALLED, app); - } else { - if (preferences != null && saveToSharedPreferences) - preferences.edit().putBoolean(preferenceSaveResult, false).apply(); - verifyCallback.dontAllow(PiracyCheckerError.NOT_LICENSED, null); - } - } - } - - private void dismissDialog() { - if (dialog != null) { - dialog.dismiss(); - dialog = null; - } - } - - private void destroyLVLChecker() { - if (libraryLVLChecker != null) { - libraryLVLChecker.finishAllChecks(); - libraryLVLChecker.onDestroy(); - libraryLVLChecker = null; - } - } -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/PiracyChecker.kt b/library/src/main/java/com/github/javiersantos/piracychecker/PiracyChecker.kt new file mode 100644 index 0000000..0e704b1 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/PiracyChecker.kt @@ -0,0 +1,470 @@ +package com.github.javiersantos.piracychecker + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.provider.Settings +import android.support.annotation.ColorRes +import android.support.annotation.LayoutRes +import android.support.annotation.StringRes +import android.util.Log +import com.github.javiersantos.licensing.AESObfuscator +import com.github.javiersantos.licensing.LibraryChecker +import com.github.javiersantos.licensing.LibraryCheckerCallback +import com.github.javiersantos.licensing.ServerManagedPolicy +import com.github.javiersantos.piracychecker.activities.LicenseActivity +import com.github.javiersantos.piracychecker.callbacks.AllowCallback +import com.github.javiersantos.piracychecker.callbacks.DoNotAllowCallback +import com.github.javiersantos.piracychecker.callbacks.OnErrorCallback +import com.github.javiersantos.piracychecker.callbacks.PiracyCheckerCallback +import com.github.javiersantos.piracychecker.enums.AppType +import com.github.javiersantos.piracychecker.enums.Display +import com.github.javiersantos.piracychecker.enums.InstallerID +import com.github.javiersantos.piracychecker.enums.PiracyCheckerError +import com.github.javiersantos.piracychecker.enums.PirateApp +import com.github.javiersantos.piracychecker.utils.SaltUtils +import com.github.javiersantos.piracychecker.utils.getPirateApp +import com.github.javiersantos.piracychecker.utils.isDebug +import com.github.javiersantos.piracychecker.utils.isInEmulator +import com.github.javiersantos.piracychecker.utils.verifyInstallerId +import com.github.javiersantos.piracychecker.utils.verifySigningCertificate +import java.util.ArrayList +import java.util.Arrays + +// Library configuration/customizations +@Suppress("unused") +@SuppressLint("HardwareIds") +class PiracyChecker( + private var context: Context?, + var unlicensedDialogTitle: String? = + context?.getString(R.string.app_unlicensed).orEmpty(), + var unlicensedDialogDescription: String? = + context?.getString(R.string.app_unlicensed_description).orEmpty() + ) { + + private var display: Display? = null + @ColorRes + private var colorPrimary: Int = 0 + @ColorRes + private var colorPrimaryDark: Int = 0 + private var withLightStatusBar: Boolean = false + @LayoutRes + private var layoutXML = -1 + private var enableLVL: Boolean = false + private var enableSigningCertificate: Boolean = false + private var enableUnauthorizedAppsCheck: Boolean = false + private var enableStoresCheck: Boolean = false + private var enableEmulatorCheck: Boolean = false + private var enableDeepEmulatorCheck: Boolean = false + private var enableDebugCheck: Boolean = false + private var enableFoldersCheck: Boolean = false + private var enableAPKCheck: Boolean = false + private var saveToSharedPreferences: Boolean = false + private var blockUnauthorized: Boolean = false + private var preferences: SharedPreferences? = null + private var preferenceSaveResult: String? = null + private var preferenceBlockUnauthorized: String? = null + private var licenseBase64: String? = null + private var signature: String? = null + private val installerIDs: MutableList + private val extraApps: ArrayList + + private var allowCallback: AllowCallback? = null + private var doNotAllowCallback: DoNotAllowCallback? = null + private var onErrorCallback: OnErrorCallback? = null + + // LVL + private var libraryLVLChecker: LibraryChecker? = null + // Dialog + private var dialog: PiracyCheckerDialog? = null + + init { + this.display = Display.DIALOG + this.installerIDs = ArrayList() + this.extraApps = ArrayList() + this.colorPrimary = R.color.colorPrimary + this.colorPrimaryDark = R.color.colorPrimaryDark + } + + constructor(context: Context?) : + this( + context, context?.getString(R.string.app_unlicensed).orEmpty(), + context?.getString(R.string.app_unlicensed_description).orEmpty()) + + constructor(context: Context?, title: String?) : + this( + context, title.orEmpty(), + context?.getString(R.string.app_unlicensed_description).orEmpty()) + + constructor(context: Context?, @StringRes title: Int) : + this( + context, + if (title != 0) context?.getString(title).orEmpty() else "") + + constructor(context: Context?, @StringRes title: Int, @StringRes description: Int) : + this( + context, + if (title != 0) context?.getString(title).orEmpty() else "", + if (description != 0) context?.getString(description).orEmpty() else "") + + fun enableGooglePlayLicensing(licenseKeyBase64: String): PiracyChecker { + this.enableLVL = true + this.licenseBase64 = licenseKeyBase64 + return this + } + + fun enableSigningCertificate(signature: String): PiracyChecker { + this.enableSigningCertificate = true + this.signature = signature + return this + } + + fun enableInstallerId(vararg installerID: InstallerID): PiracyChecker { + this.installerIDs.addAll(Arrays.asList(*installerID)) + return this + } + + fun enableUnauthorizedAppsCheck(enable: Boolean = true): PiracyChecker { + this.enableUnauthorizedAppsCheck = enable + return this + } + + fun blockIfUnauthorizedAppUninstalled( + preferences: SharedPreferences, + preferenceName: String + ): PiracyChecker { + this.blockUnauthorized = true + this.preferenceBlockUnauthorized = preferenceName + saveToSharedPreferences(preferences) + return this + } + + fun blockIfUnauthorizedAppUninstalled( + preferencesName: String, + preferenceName: String + ): PiracyChecker { + this.blockUnauthorized = true + this.preferenceBlockUnauthorized = preferenceName + saveToSharedPreferences(preferencesName) + return this + } + + fun enableStoresCheck(enable: Boolean = true): PiracyChecker { + this.enableStoresCheck = enable + return this + } + + fun enableDebugCheck(enable: Boolean = true): PiracyChecker { + this.enableDebugCheck = enable + return this + } + + fun enableAPKCheck(enable: Boolean = true): PiracyChecker { + this.enableAPKCheck = enable + return this + } + + fun enableEmulatorCheck(deepCheck: Boolean = true): PiracyChecker { + this.enableEmulatorCheck = true + this.enableDeepEmulatorCheck = deepCheck + return this + } + + fun enableFoldersCheck(foldersCheck: Boolean = true): PiracyChecker { + this.enableFoldersCheck = foldersCheck + return this + } + + fun addAppToCheck(vararg apps: PirateApp): PiracyChecker { + this.extraApps.addAll(Arrays.asList(*apps)) + return this + } + + fun addAppToCheck(app: PirateApp): PiracyChecker { + this.extraApps.add(app) + return this + } + + fun saveResultToSharedPreferences( + preferences: SharedPreferences, + preferenceName: String + ): PiracyChecker { + this.saveToSharedPreferences = true + this.preferenceSaveResult = preferenceName + saveToSharedPreferences(preferences) + return this + } + + fun saveResultToSharedPreferences( + preferencesName: String, + preferenceName: String + ): PiracyChecker { + this.saveToSharedPreferences = true + this.preferenceSaveResult = preferenceName + saveToSharedPreferences(preferencesName) + return this + } + + private fun saveToSharedPreferences(preferences: SharedPreferences?) { + if (preferences != null) { + this.preferences = preferences + } else { + try { + this.preferences = (context as Activity).getPreferences(Context.MODE_PRIVATE) + } catch (e: Exception) { + this.preferences = context?.getSharedPreferences( + LIBRARY_PREFERENCES_NAME, + Context.MODE_PRIVATE) + } + + } + } + + private fun saveToSharedPreferences(preferencesName: String?) { + if (preferencesName != null) { + this.preferences = context?.getSharedPreferences(preferencesName, Context.MODE_PRIVATE) + } else { + try { + this.preferences = (context as Activity).getPreferences(Context.MODE_PRIVATE) + } catch (e: Exception) { + this.preferences = context?.getSharedPreferences( + LIBRARY_PREFERENCES_NAME, + Context.MODE_PRIVATE) + } + + } + } + + fun display(display: Display): PiracyChecker { + this.display = display + return this + } + + fun withActivityColors( + @ColorRes colorPrimary: Int, + @ColorRes colorPrimaryDark: Int, + withLightStatusBar: Boolean + ): PiracyChecker { + this.colorPrimary = colorPrimary + this.colorPrimaryDark = colorPrimaryDark + this.withLightStatusBar = withLightStatusBar + return this + } + + fun withActivityLayout(@LayoutRes layout: Int): PiracyChecker { + this.layoutXML = layout + return this + } + + fun allowCallback(allowCallback: AllowCallback): PiracyChecker { + this.allowCallback = allowCallback + return this + } + + fun doNotAllowCallback(doNotAllowCallback: DoNotAllowCallback): PiracyChecker { + this.doNotAllowCallback = doNotAllowCallback + return this + } + + fun onErrorCallback(errorCallback: OnErrorCallback): PiracyChecker { + this.onErrorCallback = errorCallback + return this + } + + fun callback(callback: PiracyCheckerCallback): PiracyChecker { + this.allowCallback = object : AllowCallback { + override fun allow() { + callback.allow() + } + } + this.doNotAllowCallback = object : DoNotAllowCallback { + override fun doNotAllow(error: PiracyCheckerError, app: PirateApp?) { + callback.doNotAllow(error, app) + } + } + this.onErrorCallback = object : OnErrorCallback { + override fun onError(error: PiracyCheckerError) { + super.onError(error) + callback.onError(error) + } + } + return this + } + + fun destroy() { + dismissDialog() + destroyLVLChecker() + context = null + } + + fun start() { + if (allowCallback == null && doNotAllowCallback == null) { + callback(object : PiracyCheckerCallback() { + override fun allow() {} + + override fun doNotAllow(error: PiracyCheckerError, app: PirateApp?) { + if (context is Activity && (context as Activity).isFinishing) { + return + } + + val dialogContent = when { + app != null -> + context?.getString(R.string.unauthorized_app_found, app.name).orEmpty() + error == PiracyCheckerError.BLOCK_PIRATE_APP -> + context?.getString(R.string.unauthorized_app_blocked).orEmpty() + else -> unlicensedDialogDescription + } + + if (display == Display.DIALOG) { + dismissDialog() + dialog = PiracyCheckerDialog.newInstance( + unlicensedDialogTitle.orEmpty(), dialogContent.orEmpty()) + + context?.let { + dialog?.show(it) ?: { + Log.e( + "PiracyChecker", + "Unlicensed dialog was not built properly. Make sure your context is an instance of Activity") + }() + } + } else { + val intent = Intent(context, LicenseActivity::class.java) + .putExtra("content", dialogContent) + .putExtra("colorPrimary", colorPrimary) + .putExtra("colorPrimaryDark", colorPrimaryDark) + .putExtra("withLightStatusBar", withLightStatusBar) + .putExtra("layoutXML", layoutXML) + context?.startActivity(intent) + if (context is Activity) { + (context as Activity).finish() + } + destroy() + } + } + }) + } + verify() + } + + private fun verify() { + // Library will check first the non-LVL methods since LVL is asynchronous and could take + // some seconds to give a result + if (!verifySigningCertificate()) { + doNotAllowCallback?.doNotAllow(PiracyCheckerError.SIGNATURE_NOT_VALID, null) + } else if (!verifyInstallerId()) { + doNotAllowCallback?.doNotAllow(PiracyCheckerError.INVALID_INSTALLER_ID, null) + } else if (!verifyUnauthorizedApp()) { + doNotAllowCallback?.doNotAllow(PiracyCheckerError.BLOCK_PIRATE_APP, null) + } else { + if (enableLVL) { + val deviceId = + Settings.Secure.getString(context?.contentResolver, Settings.Secure.ANDROID_ID) + destroyLVLChecker() + libraryLVLChecker = + LibraryChecker( + context, + ServerManagedPolicy( + context, + AESObfuscator( + SaltUtils.getSalt(context), context?.packageName, deviceId)), + licenseBase64) + libraryLVLChecker?.checkAccess(object : LibraryCheckerCallback { + override fun allow(reason: Int) { + doExtraVerification(true) + } + + override fun dontAllow(reason: Int) { + doExtraVerification(false) + } + + override fun applicationError(errorCode: Int) { + onErrorCallback?.onError( + PiracyCheckerError.getCheckerErrorFromCode(errorCode)) + } + }) + } else { + doExtraVerification(true) + } + } + } + + private fun verifySigningCertificate(): Boolean { + return !enableSigningCertificate || (context?.verifySigningCertificate(signature) == true) + } + + private fun verifyInstallerId(): Boolean { + return installerIDs.isEmpty() || (context?.verifyInstallerId(installerIDs) == true) + } + + private fun verifyUnauthorizedApp(): Boolean { + return !blockUnauthorized || + !(preferences?.getBoolean(preferenceBlockUnauthorized, false) ?: false) + } + + private fun doExtraVerification( + possibleSuccess: Boolean + ) { + val app = context?.getPirateApp( + enableUnauthorizedAppsCheck, enableStoresCheck, enableFoldersCheck, enableAPKCheck, + extraApps) + if (possibleSuccess) { + if (enableDebugCheck && (context?.isDebug() == true)) { + if (preferences != null && saveToSharedPreferences) + preferences?.edit()?.putBoolean(preferenceSaveResult, false)?.apply() + doNotAllowCallback?.doNotAllow(PiracyCheckerError.USING_DEBUG_APP, null) + } else if (enableEmulatorCheck && isInEmulator( + enableDeepEmulatorCheck)) { + if (preferences != null && saveToSharedPreferences) + preferences?.edit()?.putBoolean(preferenceSaveResult, false)?.apply() + doNotAllowCallback?.doNotAllow(PiracyCheckerError.USING_APP_IN_EMULATOR, null) + } else if (app != null) { + if (preferences != null && saveToSharedPreferences) + preferences?.edit()?.putBoolean(preferenceSaveResult, false)?.apply() + if (preferences != null && blockUnauthorized && app.type == AppType.PIRATE) + preferences?.edit()?.putBoolean(preferenceBlockUnauthorized, true)?.apply() + doNotAllowCallback?.doNotAllow( + if (app.type == AppType.STORE) + PiracyCheckerError.THIRD_PARTY_STORE_INSTALLED + else + PiracyCheckerError.PIRATE_APP_INSTALLED, app) + } else { + if (preferences != null && saveToSharedPreferences) + preferences?.edit()?.putBoolean(preferenceSaveResult, true)?.apply() + allowCallback?.allow() + } + } else { + if (app != null) { + if (preferences != null && saveToSharedPreferences) + preferences?.edit()?.putBoolean(preferenceSaveResult, false)?.apply() + if (preferences != null && blockUnauthorized && app.type == AppType.PIRATE) + preferences?.edit()?.putBoolean(preferenceBlockUnauthorized, true)?.apply() + doNotAllowCallback?.doNotAllow( + if (app.type == AppType.STORE) + PiracyCheckerError.THIRD_PARTY_STORE_INSTALLED + else + PiracyCheckerError.PIRATE_APP_INSTALLED, app) + } else { + if (preferences != null && saveToSharedPreferences) + preferences?.edit()?.putBoolean(preferenceSaveResult, false)?.apply() + doNotAllowCallback?.doNotAllow(PiracyCheckerError.NOT_LICENSED, null) + } + } + } + + private fun dismissDialog() { + dialog?.dismiss() + dialog = null + } + + private fun destroyLVLChecker() { + libraryLVLChecker?.finishAllChecks() + libraryLVLChecker?.onDestroy() + libraryLVLChecker = null + } + + companion object { + private const val LIBRARY_PREFERENCES_NAME = "license_check" + } +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerDialog.java b/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerDialog.java deleted file mode 100644 index ef8f509..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerDialog.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.javiersantos.piracychecker; - -import android.app.Activity; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.Context; -import android.os.Bundle; -import android.support.annotation.NonNull; - -public class PiracyCheckerDialog extends DialogFragment { - private static PiracyCheckerDialog dialog; - private static String mTitle, mContent; - - public PiracyCheckerDialog() { - } - - public static PiracyCheckerDialog newInstance(String dialogTitle, String dialogContent) { - dialog = new PiracyCheckerDialog(); - mTitle = dialogTitle; - mContent = dialogContent; - - return dialog; - } - - protected void show(Context context) { - dialog.show(((Activity) context).getFragmentManager(), "[LICENSE_DIALOG]"); - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - setCancelable(false); - return LibraryUtils.buildUnlicensedDialog(getActivity(), mTitle, - mContent); - } - -} diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerDialog.kt b/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerDialog.kt new file mode 100644 index 0000000..d4ac958 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerDialog.kt @@ -0,0 +1,34 @@ +package com.github.javiersantos.piracychecker + +import android.app.Activity +import android.app.Dialog +import android.app.DialogFragment +import android.content.Context +import android.os.Bundle +import com.github.javiersantos.piracychecker.utils.buildUnlicensedDialog + +class PiracyCheckerDialog : DialogFragment() { + fun show(context: Context) { + (context as? Activity)?.let { + pcDialog?.show(it.fragmentManager, "[LICENSE_DIALOG]") + } + } + + override fun onCreateDialog(savedInstanceState: Bundle): Dialog? { + isCancelable = false + return activity.buildUnlicensedDialog(title.orEmpty(), content.orEmpty()) + } + + companion object { + private var pcDialog: PiracyCheckerDialog? = null + private var title: String? = null + private var content: String? = null + + fun newInstance(dialogTitle: String, dialogContent: String): PiracyCheckerDialog? { + pcDialog = PiracyCheckerDialog() + title = dialogTitle + content = dialogContent + return pcDialog + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerUtils.java b/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerUtils.java deleted file mode 100644 index e0f8a19..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/PiracyCheckerUtils.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.javiersantos.piracychecker; - -import android.content.Context; - -public class PiracyCheckerUtils { - public static String getAPKSignature(Context context) { - return LibraryUtils.getCurrentSignature(context); - } -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/SaltUtils.java b/library/src/main/java/com/github/javiersantos/piracychecker/SaltUtils.java deleted file mode 100644 index 8717f82..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/SaltUtils.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.github.javiersantos.piracychecker; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import java.util.Random; - -/** - * Credits to Aidan Follestad (afollestad) - */ -class SaltUtils { - private static final String KEY_SALT = "piracy-salt"; - private static byte[] mSalt; - - private static void generateSalt(Context context) { - mSalt = new byte[20]; - final Random randomGenerator = new Random(); - for (int i = 0; i < 20; ++i) { - mSalt[i] = (byte) (randomGenerator.nextInt(600) - 300); - } - final String saltStr = getSaltString(); - PreferenceManager.getDefaultSharedPreferences(context) - .edit().putString(KEY_SALT, saltStr).apply(); - } - - private static String getSaltString() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mSalt.length; i++) { - if (i > 0) { - sb.append(" "); - } - sb.append(Byte.toString(mSalt[i])); - } - return sb.toString(); - } - - private static byte[] bytesFromString(String string) { - final String[] split = string.split(" "); - final byte[] data = new byte[split.length]; - for (int i = 0; i < split.length; i++) { - data[i] = Byte.parseByte(split[i]); - } - return data; - } - - @SuppressWarnings("ConstantConditions") - static byte[] getSalt(Context context) { - if (mSalt == null) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - if (prefs.contains(KEY_SALT)) { - mSalt = bytesFromString(prefs.getString(KEY_SALT, null)); - } - if (mSalt == null) { - generateSalt(context); - } - } - return mSalt; - } -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/activities/ActivityUtils.java b/library/src/main/java/com/github/javiersantos/piracychecker/activities/ActivityUtils.java deleted file mode 100644 index b41269d..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/activities/ActivityUtils.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.javiersantos.piracychecker.activities; - -import android.content.Context; -import android.os.Build; -import android.support.annotation.NonNull; -import android.view.View; - -class ActivityUtils { - - static String getAppName(Context context) { - return context.getString(context.getApplicationInfo().labelRes); - } - - static void setupLightStatusBar(@NonNull View view, boolean enable) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - int flags = view.getSystemUiVisibility(); - if (enable) - flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - else - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - view.setSystemUiVisibility(flags); - } - } - -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/activities/ActivityUtils.kt b/library/src/main/java/com/github/javiersantos/piracychecker/activities/ActivityUtils.kt new file mode 100644 index 0000000..7a14cd5 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/activities/ActivityUtils.kt @@ -0,0 +1,18 @@ +package com.github.javiersantos.piracychecker.activities + +import android.content.Context +import android.os.Build +import android.view.View + +fun Context.getAppName(): String = getString(applicationInfo.labelRes) + +fun View.setupLightStatusBar(enable: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + var flags = systemUiVisibility + flags = if (enable) + flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + else + flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() + systemUiVisibility = flags + } +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/activities/LicenseActivity.java b/library/src/main/java/com/github/javiersantos/piracychecker/activities/LicenseActivity.java deleted file mode 100644 index aa131f0..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/activities/LicenseActivity.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.github.javiersantos.piracychecker.activities; - -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.ColorRes; -import android.support.annotation.LayoutRes; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.github.javiersantos.piracychecker.R; - -public class LicenseActivity extends AppCompatActivity { - private String description; - @ColorRes - private int colorPrimary; - @ColorRes - private int colorPrimaryDark; - private boolean withLightStatusBar; - @LayoutRes - private int layoutXML; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_license); - getIntentData(); - setActivityStyle(); - setActivityData(); - } - - private void getIntentData() { - if (getIntent() != null) { - description = getIntent().getStringExtra("content"); - colorPrimary = getIntent().getIntExtra("colorPrimary", - ContextCompat - .getColor(this, R.color.colorPrimary)); - colorPrimaryDark = getIntent().getIntExtra("colorPrimaryDark", - ContextCompat.getColor(this, - R.color.colorPrimaryDark)); - withLightStatusBar = getIntent().getBooleanExtra("withLightStatusBar", false); - layoutXML = getIntent().getIntExtra("layoutXML", -1); - } - } - - private void setActivityStyle() { - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - if (toolbar != null) { - toolbar.setBackgroundColor(ContextCompat.getColor(this, colorPrimary)); - setSupportActionBar(toolbar); - - if (getSupportActionBar() != null) { - getSupportActionBar().setTitle(ActivityUtils.getAppName(this)); - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); - } - - ActivityUtils.setupLightStatusBar(getWindow().getDecorView(), withLightStatusBar); - } - - private void setActivityData() { - FrameLayout frameLayout = (FrameLayout) findViewById(R.id.mainContainer); - - LayoutInflater factory = LayoutInflater.from(this); - View inflateView; - if (layoutXML == -1) { - inflateView = factory.inflate(R.layout.activity_license_default, null); - TextView activityDescription = - (TextView) inflateView.findViewById(R.id.piracy_checker_description); - activityDescription.setText(description); - } else - inflateView = factory.inflate(layoutXML, null); - - frameLayout.addView(inflateView); - } - -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/activities/LicenseActivity.kt b/library/src/main/java/com/github/javiersantos/piracychecker/activities/LicenseActivity.kt new file mode 100644 index 0000000..5214d6d --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/activities/LicenseActivity.kt @@ -0,0 +1,77 @@ +package com.github.javiersantos.piracychecker.activities + +import android.annotation.SuppressLint +import android.os.Build +import android.os.Bundle +import android.support.annotation.ColorRes +import android.support.annotation.LayoutRes +import android.support.v4.content.ContextCompat +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.Toolbar +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.TextView + +import com.github.javiersantos.piracychecker.R + +class LicenseActivity : AppCompatActivity() { + private var description: String? = null + @ColorRes + private var colorPrimary: Int = 0 + @ColorRes + private var colorPrimaryDark: Int = 0 + private var withLightStatusBar: Boolean = false + @LayoutRes + private var layoutXML: Int = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_license) + getIntentData() + setActivityStyle() + setActivityData() + } + + private fun getIntentData() { + if (intent != null) { + description = intent.getStringExtra("content") + colorPrimary = intent.getIntExtra( + "colorPrimary", ContextCompat.getColor(this, R.color.colorPrimary)) + colorPrimaryDark = intent.getIntExtra( + "colorPrimaryDark", ContextCompat.getColor(this, R.color.colorPrimaryDark)) + withLightStatusBar = intent.getBooleanExtra("withLightStatusBar", false) + layoutXML = intent.getIntExtra("layoutXML", -1) + } + } + + private fun setActivityStyle() { + val toolbar = findViewById(R.id.toolbar) as? Toolbar + toolbar?.setBackgroundColor(ContextCompat.getColor(this, colorPrimary)) + setSupportActionBar(toolbar) + supportActionBar?.title = getAppName() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window.statusBarColor = ContextCompat.getColor(this, colorPrimaryDark) + } + + window.decorView.setupLightStatusBar(withLightStatusBar) + } + + @SuppressLint("InflateParams") + private fun setActivityData() { + val frameLayout = findViewById(R.id.mainContainer) as FrameLayout + + val factory = LayoutInflater.from(this) + val inflateView: View + if (layoutXML == -1) { + inflateView = factory.inflate(R.layout.activity_license_default, null) + val activityDescription = + inflateView.findViewById(R.id.piracy_checker_description) as TextView + activityDescription.text = description + } else + inflateView = factory.inflate(layoutXML, null) + + frameLayout.addView(inflateView) + } +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/callbacks/PiracyCheckerCallbacks.kt b/library/src/main/java/com/github/javiersantos/piracychecker/callbacks/PiracyCheckerCallbacks.kt new file mode 100644 index 0000000..51e431c --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/callbacks/PiracyCheckerCallbacks.kt @@ -0,0 +1,47 @@ +package com.github.javiersantos.piracychecker.callbacks + +import com.github.javiersantos.piracychecker.enums.PiracyCheckerError +import com.github.javiersantos.piracychecker.enums.PirateApp + +interface AllowCallback { + /** + * Called after the app checked as valid and licensed + */ + fun allow() +} + +interface DoNotAllowCallback { + @Deprecated("dontAllow has been deprecated in favor of doNotAllow", ReplaceWith("doNotAllow")) + fun dontAllow(error: PiracyCheckerError, app: PirateApp?) = + doNotAllow(error, app) + + /** + * Called if the app is not valid or the user is using an unlicensed version. Check errors at + * [PiracyCheckerError]. + * + * @param error + * PiracyCheckerError.NOT_LICENSED, PiracyCheckerError.SIGNATURE_NOT_VALID or + * PiracyCheckerError.INVALID_INSTALLER_ID + * @param app + * The [PirateApp] that has been detected on device. Returns null in no app was + * found. + */ + fun doNotAllow(error: PiracyCheckerError, app: PirateApp?) +} + +interface OnErrorCallback { + /** + * Called if an error with the license check occurs. Check errors at [ ]. + * + * @param error + * PiracyCheckerError.INVALID_PACKAGE_NAME, PiracyCheckerError.NON_MATCHING_UID, + * PiracyCheckerError.NOT_MARKET_MANAGED, PiracyCheckerError.CHECK_IN_PROGRESS, + * PiracyCheckerError.INVALID_PUBLIC_KEY, PiracyCheckerError.MISSING_PERMISSION or + * PiracyCheckerError.UNKNOWN + */ + fun onError(error: PiracyCheckerError) {} +} + +abstract class PiracyCheckerCallback : AllowCallback, + DoNotAllowCallback, + OnErrorCallback \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/callbacks/PiracyCheckerCallbacksDSL.kt b/library/src/main/java/com/github/javiersantos/piracychecker/callbacks/PiracyCheckerCallbacksDSL.kt new file mode 100644 index 0000000..0b9d05b --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/callbacks/PiracyCheckerCallbacksDSL.kt @@ -0,0 +1,14 @@ +package com.github.javiersantos.piracychecker.callbacks + +import com.github.javiersantos.piracychecker.PiracyChecker + +class PiracyCheckerCallbacksDSL internal constructor(private val checker: PiracyChecker) { + fun allow(allowCallback: AllowCallback): PiracyChecker = + checker.allowCallback(allowCallback) + + fun doNotAllow(doNotAllowCallback: DoNotAllowCallback): PiracyChecker = + checker.doNotAllowCallback(doNotAllowCallback) + + fun onError(onErrorCallback: OnErrorCallback): PiracyChecker = + checker.onErrorCallback(onErrorCallback) +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/AppType.java b/library/src/main/java/com/github/javiersantos/piracychecker/enums/AppType.java deleted file mode 100644 index 5e34ccf..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/enums/AppType.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.javiersantos.piracychecker.enums; - -public enum AppType { - PIRATE, - STORE, - OTHER -} diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/AppType.kt b/library/src/main/java/com/github/javiersantos/piracychecker/enums/AppType.kt new file mode 100644 index 0000000..cc456a4 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/enums/AppType.kt @@ -0,0 +1,3 @@ +package com.github.javiersantos.piracychecker.enums + +enum class AppType { PIRATE, STORE, OTHER } \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/Display.java b/library/src/main/java/com/github/javiersantos/piracychecker/enums/Display.java deleted file mode 100644 index 6d9af17..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/enums/Display.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.javiersantos.piracychecker.enums; - -public enum Display { - DIALOG, - ACTIVITY -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/Display.kt b/library/src/main/java/com/github/javiersantos/piracychecker/enums/Display.kt new file mode 100644 index 0000000..b777ef5 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/enums/Display.kt @@ -0,0 +1,3 @@ +package com.github.javiersantos.piracychecker.enums + +enum class Display { DIALOG, ACTIVITY } \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/InstallerID.java b/library/src/main/java/com/github/javiersantos/piracychecker/enums/InstallerID.java deleted file mode 100644 index 1728c01..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/enums/InstallerID.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.javiersantos.piracychecker.enums; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public enum InstallerID { - GOOGLE_PLAY("com.android.vending|com.google.android.feedback"), - AMAZON_APP_STORE("com.amazon.venezia"), - GALAXY_APPS("com.sec.android.app.samsungapps"); - - private final String text; - - InstallerID(final String text) { - this.text = text; - } - - /* (non-Javadoc) - * @see java.lang.Enum#toString() - */ - @Override - public String toString() { - return text; - } - - public List toIDs() { - if (text.contains("|")) { - String[] split = text.split("\\|"); - return new ArrayList<>(Arrays.asList(split)); - } else { - return new ArrayList<>(Collections.singletonList(text)); - } - } - -} diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/InstallerID.kt b/library/src/main/java/com/github/javiersantos/piracychecker/enums/InstallerID.kt new file mode 100644 index 0000000..a451b60 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/enums/InstallerID.kt @@ -0,0 +1,24 @@ +package com.github.javiersantos.piracychecker.enums + +import java.util.ArrayList +import java.util.Arrays + +enum class InstallerID(private val text: String) { + GOOGLE_PLAY("com.android.vending|com.google.android.feedback"), + AMAZON_APP_STORE("com.amazon.venezia"), + GALAXY_APPS("com.sec.android.app.samsungapps"); + + /* (non-Javadoc) + * @see java.lang.Enum#toString() + */ + override fun toString(): String { + return text + } + + fun toIDs(): List = if (text.contains("|")) { + val split = text.split("\\|".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + ArrayList(Arrays.asList(*split)) + } else { + ArrayList(listOf(text)) + } +} diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerCallback.java b/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerCallback.java deleted file mode 100644 index 5dd684e..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerCallback.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.github.javiersantos.piracychecker.enums; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -public abstract class PiracyCheckerCallback { - - /** - * Called after the app checked as valid and licensed - */ - public abstract void allow(); - - /** - * Called if the app is not valid or the user is using an unlicensed version. Check errors at - * {@link PiracyCheckerError}. - * - * @param error - * PiracyCheckerError.NOT_LICENSED, PiracyCheckerError.SIGNATURE_NOT_VALID or - * PiracyCheckerError.INVALID_INSTALLER_ID - * @param app - * The {@link PirateApp} that has been detected on device. Returns null in no app was - * found. - */ - public abstract void dontAllow(@NonNull PiracyCheckerError error, @Nullable PirateApp app); - - - /** - * Called if an error with the license check occurs. Check errors at {@link - * PiracyCheckerError}. - * - * @param error - * PiracyCheckerError.INVALID_PACKAGE_NAME, PiracyCheckerError.NON_MATCHING_UID, - * PiracyCheckerError.NOT_MARKET_MANAGED, PiracyCheckerError.CHECK_IN_PROGRESS, - * PiracyCheckerError.INVALID_PUBLIC_KEY, PiracyCheckerError.MISSING_PERMISSION or - * PiracyCheckerError.UNKNOWN - */ - public void onError(@NonNull PiracyCheckerError error) { - } - -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerError.java b/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerError.java deleted file mode 100644 index 5f77d10..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerError.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.javiersantos.piracychecker.enums; - -public enum PiracyCheckerError { - NOT_LICENSED("This user is not using a licensed application from Google Play."), - SIGNATURE_NOT_VALID("This app is using another signature. The original APK has been modified."), - INVALID_INSTALLER_ID("This app has been installed from a non-allowed source."), - USING_DEBUG_APP("This is a debug build."), - USING_APP_IN_EMULATOR("This app is being used in an emulator."), - PIRATE_APP_INSTALLED("At least one pirate app has been detected on device."), - BLOCK_PIRATE_APP( - "At least one pirate app has been detected and the app must be reinstalled when all " + - "unauthorized apps are uninstalled.."), - THIRD_PARTY_STORE_INSTALLED("At least one third-party store has been detected on device."), - - // Other errors - INVALID_PACKAGE_NAME("Application package name is invalid."), - NON_MATCHING_UID("Application UID doesn\'t match."), - NOT_MARKET_MANAGED("Not market managed error."), - CHECK_IN_PROGRESS("License check is in progress."), - INVALID_PUBLIC_KEY("Application public key is invalid."), - MISSING_PERMISSION("Application misses the \'com.android.vending.CHECK_LICENSE\' " + - "permission."), - UNKNOWN("Unknown error."); - - private final String text; - - PiracyCheckerError(final String text) { - this.text = text; - } - - public static PiracyCheckerError getCheckerErrorFromCode(int errorCode) { - switch (errorCode) { - case 1: - return PiracyCheckerError.INVALID_PACKAGE_NAME; - case 2: - return PiracyCheckerError.NON_MATCHING_UID; - case 3: - return PiracyCheckerError.NOT_MARKET_MANAGED; - case 4: - return PiracyCheckerError.CHECK_IN_PROGRESS; - case 5: - return PiracyCheckerError.INVALID_PUBLIC_KEY; - case 6: - return PiracyCheckerError.MISSING_PERMISSION; - default: - return PiracyCheckerError.UNKNOWN; - } - } - - /* (non-Javadoc) - * @see java.lang.Enum#toString() - */ - @Override - public String toString() { - return text; - } - -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerError.kt b/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerError.kt new file mode 100644 index 0000000..01e9e65 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/enums/PiracyCheckerError.kt @@ -0,0 +1,43 @@ +package com.github.javiersantos.piracychecker.enums + +enum class PiracyCheckerError(private val text: String) { + NOT_LICENSED("This user is not using a licensed application from Google Play."), + SIGNATURE_NOT_VALID("This app is using another signature. The original APK has been modified."), + INVALID_INSTALLER_ID("This app has been installed from a non-allowed source."), + USING_DEBUG_APP("This is a debug build."), + USING_APP_IN_EMULATOR("This app is being used in an emulator."), + PIRATE_APP_INSTALLED("At least one pirate app has been detected on device."), + BLOCK_PIRATE_APP( + "At least one pirate app has been detected and the app must be reinstalled " + + "when all unauthorized apps are uninstalled."), + THIRD_PARTY_STORE_INSTALLED("At least one third-party store has been detected on device."), + + // Other errors + INVALID_PACKAGE_NAME("Application package name is invalid."), + NON_MATCHING_UID("Application UID doesn\'t match."), + NOT_MARKET_MANAGED("Not market managed error."), + CHECK_IN_PROGRESS("License check is in progress."), + INVALID_PUBLIC_KEY("Application public key is invalid."), + MISSING_PERMISSION("Application misses the \'com.android.vending.CHECK_LICENSE\' " + "permission."), + UNKNOWN("Unknown error."); + + /* (non-Javadoc) + * @see java.lang.Enum#toString() + */ + override fun toString(): String { + return text + } + + companion object { + fun getCheckerErrorFromCode(errorCode: Int): PiracyCheckerError = when (errorCode) { + 1 -> PiracyCheckerError.INVALID_PACKAGE_NAME + 2 -> PiracyCheckerError.NON_MATCHING_UID + 3 -> PiracyCheckerError.NOT_MARKET_MANAGED + 4 -> PiracyCheckerError.CHECK_IN_PROGRESS + 5 -> PiracyCheckerError.INVALID_PUBLIC_KEY + 6 -> PiracyCheckerError.MISSING_PERMISSION + else -> PiracyCheckerError.UNKNOWN + } + } + +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/PirateApp.java b/library/src/main/java/com/github/javiersantos/piracychecker/enums/PirateApp.java deleted file mode 100644 index 1ba7b05..0000000 --- a/library/src/main/java/com/github/javiersantos/piracychecker/enums/PirateApp.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.github.javiersantos.piracychecker.enums; - -import android.support.annotation.NonNull; -import android.text.TextUtils; - -public class PirateApp { - private String name; - private String[] pack; - private AppType type; - - public PirateApp(@NonNull String name, @NonNull String[] pack, @NonNull AppType type) { - this.name = name; - this.pack = pack; - this.type = type; - } - - public PirateApp(@NonNull String name, @NonNull String appPackage, @NonNull AppType type) { - this.name = name; - this.pack = TextUtils.split(appPackage, ""); - this.type = type; - } - - public PirateApp(@NonNull String name, @NonNull String appPackage) { - this(name, appPackage, AppType.OTHER); - } - - public String getName() { - return name; - } - - @Deprecated - public String[] getPack() { - return pack; - } - - public String getPackage() { - StringBuilder sb = new StringBuilder(); - for (String s : pack) { - sb.append(s); - } - return sb.toString(); - } - - public AppType getType() { - return type; - } -} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/enums/PirateApp.kt b/library/src/main/java/com/github/javiersantos/piracychecker/enums/PirateApp.kt new file mode 100644 index 0000000..6caf7dc --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/enums/PirateApp.kt @@ -0,0 +1,32 @@ +package com.github.javiersantos.piracychecker.enums + +import android.text.TextUtils + +class PirateApp(name: String, pack: Array, type: AppType = AppType.OTHER) { + var name: String? = null + private set + var type: AppType? = null + private set + private var pack: Array? = null + + init { + this.name = name + this.pack = pack.clone() + this.type = type + } + + @Deprecated("Deprecated in favor of packageName", ReplaceWith("packageName")) + val `package`: String + get() = packageName + + val packageName: String + get() { + val sb = StringBuilder() + pack?.forEach { sb.append(it) } + return sb.toString() + } + + @JvmOverloads + constructor(name: String, appPackage: String, type: AppType = AppType.OTHER) : + this(name, TextUtils.split(appPackage, ""), type) +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/utils/LibraryUtils.kt b/library/src/main/java/com/github/javiersantos/piracychecker/utils/LibraryUtils.kt new file mode 100644 index 0000000..b20c304 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/utils/LibraryUtils.kt @@ -0,0 +1,541 @@ +package com.github.javiersantos.piracychecker.utils + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.opengl.GLES20 +import android.os.Build +import android.os.Environment +import android.support.v4.app.ActivityCompat +import android.support.v4.app.Fragment +import android.support.v7.app.AlertDialog +import android.util.Base64 +import com.github.javiersantos.piracychecker.R +import com.github.javiersantos.piracychecker.enums.AppType +import com.github.javiersantos.piracychecker.enums.InstallerID +import com.github.javiersantos.piracychecker.enums.PirateApp +import java.io.File +import java.security.MessageDigest +import java.util.ArrayList + +internal fun Context.buildUnlicensedDialog(title: String, content: String): AlertDialog? { + return (this as? Activity)?.let { + if (isFinishing) return null + AlertDialog.Builder(this) + .setCancelable(false) + .setTitle(title) + .setMessage(content) + .setPositiveButton( + getString(R.string.app_unlicensed_close), + DialogInterface.OnClickListener { _, _ -> + if (isFinishing) + return@OnClickListener + finish() + }) + .create() + } +} + +val Context.apkSignature: String + get() = currentSignature + +val Fragment.apkSignature: String + get() = context!!.apkSignature + +val Context.currentSignature: String + @SuppressLint("PackageManagerGetSignatures") + get() { + var res = "" + try { + val packageInfo = + packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) + for (signature in packageInfo.signatures) { + val messageDigest = MessageDigest.getInstance("SHA") + messageDigest.update(signature.toByteArray()) + res = Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT) + } + } catch (ignored: Exception) { + } + return res.trim() + } + +internal fun Context.verifySigningCertificate(appSignature: String?): Boolean = + appSignature?.let { currentSignature == it } ?: false + +internal fun Context.verifyInstallerId(installerID: List): Boolean { + val validInstallers = ArrayList() + val installer = packageManager.getInstallerPackageName(packageName) + for (id in installerID) { + validInstallers.addAll(id.toIDs()) + } + return installer != null && validInstallers.contains(installer) +} + +@SuppressLint("SdCardPath") +internal fun Context.getPirateApp( + lpf: Boolean, + stores: Boolean, + folders: Boolean, + apks: Boolean, + extraApps: ArrayList + ): PirateApp? { + if (!lpf && !stores && extraApps.isEmpty()) return null + + val apps = getApps(extraApps) + var installed = false + var theApp: PirateApp? = null + + try { + val pm = packageManager + val list = pm?.getInstalledApplications(PackageManager.GET_META_DATA) + for (app in apps) { + val checkLPF = lpf && app.type == AppType.PIRATE + val checkStore = stores && app.type == AppType.STORE + val checkOther = app.type == AppType.OTHER + if (checkLPF || checkStore || checkOther) { + installed = list?.any { it.packageName.contains(app.packageName) } ?: false + if (!installed) { + installed = isIntentAvailable(pm.getLaunchIntentForPackage(app.packageName)) + } + } + if (installed) { + theApp = app + break + } + } + } catch (e: Exception) { + } + + if ((folders || apks) && theApp == null) { + if (hasPermissions()) { + var apkExist = false + var foldersExist = false + var containsFolder = false + + for (app in apps) { + val pack = app.packageName + try { + if (apks) { + val file1 = File("/data/app/$pack-1/base.apk") + val file2 = File("/data/app/$pack-2/base.apk") + val file3 = File("/data/app/$pack.apk") + val file4 = File("/data/data/$pack.apk") + apkExist = file1.exists() || file2.exists() || + file3.exists() || file4.exists() + } + if (folders) { + val file5 = File("/data/data/$pack") + val file6 = + File( + "${Environment.getExternalStorageDirectory()}/Android/data/$pack") + foldersExist = file5.exists() || file6.exists() + + val appsContainer = File("/data/app/") + if (appsContainer.exists()) { + for (f in appsContainer.listFiles()) { + if (f.name.startsWith(pack)) + containsFolder = true + } + } + } + } catch (e: Exception) { + } + if (containsFolder || apkExist || foldersExist) { + theApp = app + break + } + } + } + } + + return theApp +} + +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + * + * + * Copyright (C) 2013, Vladislav Gingo Skoumal (http://www.skoumal.net) + */ +internal fun isInEmulator(deepCheck: Boolean): Boolean { + var ratingCheckEmulator = 0 + + if (Build.PRODUCT.contains("sdk") || + Build.PRODUCT.contains("Andy") || + Build.PRODUCT.contains("ttVM_Hdragon") || + Build.PRODUCT.contains("google_sdk") || + Build.PRODUCT.contains("Droid4X") || + Build.PRODUCT.contains("nox") || + Build.PRODUCT.contains("sdk_x86") || + Build.PRODUCT.contains("sdk_google") || + Build.PRODUCT.contains("vbox86p")) { + ratingCheckEmulator++ + } + + if (Build.MANUFACTURER == "unknown" || + Build.MANUFACTURER == "Genymotion" || + Build.MANUFACTURER.contains("Andy") || + Build.MANUFACTURER.contains("MIT") || + Build.MANUFACTURER.contains("nox") || + Build.MANUFACTURER.contains("TiantianVM")) { + ratingCheckEmulator++ + } + + if (Build.BRAND == "generic" || + Build.BRAND == "generic_x86" || + Build.BRAND == "TTVM" || + Build.BRAND.contains("Andy")) { + ratingCheckEmulator++ + } + + if (Build.DEVICE.contains("generic") || + Build.DEVICE.contains("generic_x86") || + Build.DEVICE.contains("Andy") || + Build.DEVICE.contains("ttVM_Hdragon") || + Build.DEVICE.contains("Droid4X") || + Build.DEVICE.contains("nox") || + Build.DEVICE.contains("generic_x86_64") || + Build.DEVICE.contains("vbox86p")) { + ratingCheckEmulator++ + } + + if (Build.MODEL == "sdk" || + Build.MODEL == "google_sdk" || + Build.MODEL.contains("Droid4X") || + Build.MODEL.contains("TiantianVM") || + Build.MODEL.contains("Andy") || + Build.MODEL == "Android SDK built for x86_64" || + Build.MODEL == "Android SDK built for x86") { + ratingCheckEmulator++ + } + + if (Build.HARDWARE == "goldfish" || + Build.HARDWARE == "vbox86" || + Build.HARDWARE.contains("nox") || + Build.HARDWARE.contains("ttVM_x86")) { + ratingCheckEmulator++ + } + + if (Build.FINGERPRINT.contains("generic") || + Build.FINGERPRINT.contains("generic/sdk/generic") || + Build.FINGERPRINT.contains("generic_x86/sdk_x86/generic_x86") || + Build.FINGERPRINT.contains("Andy") || + Build.FINGERPRINT.contains("ttVM_Hdragon") || + Build.FINGERPRINT.contains("generic_x86_64") || + Build.FINGERPRINT.contains("generic/google_sdk/generic") || + Build.FINGERPRINT.contains("vbox86p") || + Build.FINGERPRINT.contains("generic/vbox86p/vbox86p")) { + ratingCheckEmulator++ + } + + if (deepCheck) { + try { + val opengl = GLES20.glGetString(GLES20.GL_RENDERER) + if (opengl != null) { + if (opengl.contains("Bluestacks") || opengl.contains("Translator")) + ratingCheckEmulator += 10 + } + } catch (ignored: Exception) { + } + + try { + val sharedFolder = File( + "${Environment.getExternalStorageDirectory()}${File.separatorChar}windows${File.separatorChar}BstSharedFolder") + if (sharedFolder.exists()) + ratingCheckEmulator += 10 + } catch (ignored: Exception) { + } + } + + return ratingCheckEmulator > 3 +} + +internal fun Context.isDebug(): Boolean = + applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0 + +private fun getApps(extraApps: ArrayList): ArrayList { + val apps = ArrayList() + apps.add( + PirateApp( + "LuckyPatcher", + arrayOf( + "c", "o", "m", ".", "c", "h", "e", "l", "p", "u", "s", ".", "l", "a", "c", "k", "y", + "p", "a", "t", "c", "h"), + AppType.PIRATE)) + apps.add( + PirateApp( + "LuckyPatcher", + arrayOf( + "c", "o", "m", ".", "d", "i", "m", "o", "n", "v", "i", "d", "e", "o", ".", "l", "u", + "c", "k", "y", "p", "a", "t", "c", "h", "e", "r"), + AppType.PIRATE)) + apps.add( + PirateApp( + "LuckyPatcher", + arrayOf("c", "o", "m", ".", "f", "o", "r", "p", "d", "a", ".", "l", "p"), + AppType.PIRATE)) + apps.add( + PirateApp( + "LuckyPatcher", + arrayOf( + "c", "o", "m", ".", "a", "n", "d", "r", "o", "i", "d", ".", "v", "e", "n", "d", "i", + "n", "g", ".", "b", "i", "l", "l", "i", "n", "g", ".", "I", "n", "A", "p", "p", "B", + "i", "l", "l", "i", "n", "g", "S", "e", "r", "v", "i", "c", "e"), + AppType.PIRATE)) + apps.add( + PirateApp( + "LuckyPatcher", + arrayOf( + "c", "o", "m", ".", "a", "n", "d", "r", "o", "i", "d", ".", "v", "e", "n", "d", "i", + "n", "g", ".", "b", "i", "l", "l", "i", "n", "g", ".", "I", "n", "A", "p", "p", "B", + "i", "l", "l", "i", "n", "g", "S", "o", "r", "v", "i", "c", "e"), + AppType.PIRATE)) + apps.add( + PirateApp( + "LuckyPatcher", + arrayOf( + "c", "o", "m", ".", "a", "n", "d", "r", "o", "i", "d", ".", "v", "e", "n", "d", "i", + "n", "c"), + AppType.PIRATE)) + apps.add( + PirateApp( + "UretPatcher", + arrayOf( + "u", "r", "e", "t", ".", "j", "a", "s", "i", "2", "1", "6", "9", ".", "p", "a", "t", + "c", "h", "e", "r"), + AppType.PIRATE)) + apps.add( + PirateApp( + "UretPatcher", + arrayOf( + "z", "o", "n", "e", ".", "j", "a", "s", "i", "2", "1", "6", "9", ".", "u", "r", "e", + "t", "p", "a", "t", "c", "h", "e", "r"), + AppType.PIRATE)) + apps.add( + PirateApp( + "ActionLauncherPatcher", + arrayOf("p", ".", "j", "a", "s", "i", "2", "1", "6", "9", ".", "a", "l", "3"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Freedom", + arrayOf( + "c", "c", ".", "m", "a", "d", "k", "i", "t", "e", ".", "f", "r", "e", "e", "d", "o", + "m"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Freedom", + arrayOf( + "c", "c", ".", "c", "z", ".", "m", "a", "d", "k", "i", "t", "e", ".", "f", "r", "e", + "e", "d", "o", "m"), + AppType.PIRATE)) + apps.add( + PirateApp( + "CreeHack", + arrayOf( + "o", "r", "g", ".", "c", "r", "e", "e", "p", "l", "a", "y", "s", ".", "h", "a", "c", + "k"), + AppType.PIRATE)) + apps.add( + PirateApp( + "HappyMod", + arrayOf("c", "o", "m", ".", "h", "a", "p", "p", "y", "m", "o", "d", ".", "a", "p", "k"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Game Hacker", + arrayOf( + "o", "r", "g", ".", "s", "b", "t", "o", "o", "l", "s", ".", "g", "a", "m", "e", "h", + "a", "c", "k"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Game Killer Cheats", + arrayOf( + "c", "o", "m", ".", "z", "u", "n", "e", ".", "g", "a", "m", "e", "k", "i", "l", "l", + "e", "r"), + AppType.PIRATE)) + apps.add( + PirateApp( + "AGK - App Killer", + arrayOf("c", "o", "m", ".", "a", "a", "g", ".", "k", "i", "l", "l", "e", "r"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Game Killer", + arrayOf( + "c", "o", "m", ".", "k", "i", "l", "l", "e", "r", "a", "p", "p", ".", "g", "a", "m", + "e", "k", "i", "l", "l", "e", "r"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Game Killer", arrayOf("c", "n", ".", "l", "m", ".", "s", "q"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Game CheatIng Hacker", + arrayOf( + "n", "e", "t", ".", "s", "c", "h", "w", "a", "r", "z", "i", "s", ".", "g", "a", "m", + "e", "_", "c", "i", "h"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Game Hacker", + arrayOf( + "c", "o", "m", ".", "b", "a", "s", "e", "a", "p", "p", "f", "u", "l", "l", ".", "f", + "w", "d"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Content Guard Disabler", + arrayOf( + "c", "o", "m", ".", "g", "i", "t", "h", "u", "b", ".", "o", "n", "e", "m", "i", "n", + "u", "s", "o", "n", "e", ".", "d", "i", "s", "a", "b", "l", "e", "c", "o", "n", "t", + "e", "n", "t", "g", "u", "a", "r", "d"), + AppType.PIRATE)) + + apps.add( + PirateApp( + "Content Guard Disabler", + arrayOf( + "c", "o", "m", ".", "o", "n", "e", "m", "i", "n", "u", "s", "o", "n", "e", ".", "d", + "i", "s", "a", "b", "l", "e", "c", "o", "n", "t", "e", "n", "t", "g", "u", "a", "r", + "d"), + AppType.PIRATE)) + apps.add( + PirateApp( + "Aptoide", arrayOf("c", "m", ".", "a", "p", "t", "o", "i", "d", "e", ".", "p", "t"), + AppType.STORE)) + apps.add( + PirateApp( + "BlackMart", + arrayOf( + "o", "r", "g", ".", "b", "l", "a", "c", "k", "m", "a", "r", "t", ".", "m", "a", "r", + "k", "e", "t"), + AppType.STORE)) + apps.add( + PirateApp( + "BlackMart", + arrayOf( + "c", "o", "m", ".", "b", "l", "a", "c", "k", "m", "a", "r", "t", "a", "l", "p", "h", + "a"), + AppType.STORE)) + apps.add( + PirateApp( + "Mobogenie", + arrayOf("c", "o", "m", ".", "m", "o", "b", "o", "g", "e", "n", "i", "e"), + AppType.STORE)) + apps.add( + PirateApp( + "1Mobile", + arrayOf( + "m", "e", ".", "o", "n", "e", "m", "o", "b", "i", "l", "e", ".", "a", "n", "d", "r", + "o", "i", "d"), + AppType.STORE)) + apps.add( + PirateApp( + "GetApk", arrayOf( + "c", "o", "m", ".", "r", "e", "p", "o", "d", "r", "o", "i", "d", ".", "a", "p", "p"), + AppType.STORE)) + apps.add( + PirateApp( + "GetJar", + arrayOf( + "c", "o", "m", ".", "g", "e", "t", "j", "a", "r", ".", "r", "e", "w", "a", "r", "d", + "s"), + AppType.STORE)) + apps.add( + PirateApp( + "SlideMe", + arrayOf( + "c", "o", "m", ".", "s", "l", "i", "d", "e", "m", "e", ".", "s", "a", "m", ".", "m", + "a", "n", "a", "g", "e", "r"), + AppType.STORE)) + apps.add( + PirateApp( + "ACMarket", + arrayOf("n", "e", "t", ".", "a", "p", "p", "c", "a", "k", "e"), + AppType.STORE)) + apps.add( + PirateApp( + "ACMarket", + arrayOf("a", "c", ".", "m", "a", "r", "k", "e", "t", ".", "s", "t", "o", "r", "e"), + AppType.STORE)) + apps.add( + PirateApp( + "AppCake", + arrayOf("c", "o", "m", ".", "a", "p", "p", "c", "a", "k", "e"), + AppType.STORE)) + apps.add( + PirateApp( + "Z Market", + arrayOf("c", "o", "m", ".", "z", "m", "a", "p", "p"), + AppType.STORE)) + apps.add( + PirateApp( + "Modded Play Store", + arrayOf( + "c", "o", "m", ".", "d", "v", ".", "m", "a", "r", "k", "e", "t", "m", "o", "d", ".", + "i", "n", "s", "t", "a", "l", "l", "e", "r"), + AppType.STORE)) + apps.add( + PirateApp( + "Mobilism Market", + arrayOf( + "o", "r", "g", ".", "m", "o", "b", "i", "l", "i", "s", "m", ".", "a", "n", "d", "r", + "o", "i", "d"), + AppType.STORE)) + apps.add( + PirateApp( + "All-in-one Downloader", arrayOf( + "c", "o", "m", ".", "a", "l", "l", "i", "n", "o", "n", "e", ".", "f", "r", "e", "e"), + AppType.STORE)) + apps.addAll(extraApps) + return ArrayList(apps.distinctBy { it.packageName }) +} + +private fun Context.isIntentAvailable(intent: Intent?): Boolean { + return try { + val list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) + list?.isNotEmpty() ?: false + } catch (e: Exception) { + false + } +} + +private fun Context.hasPermissions(): Boolean { + return try { + Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN || + !shouldAskPermission(Manifest.permission.READ_EXTERNAL_STORAGE) || + !ActivityCompat.shouldShowRequestPermissionRationale( + this as Activity, Manifest.permission.READ_EXTERNAL_STORAGE) + } catch (e: Exception) { + false + } +} + +private fun shouldAskPermission(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + +private fun Context.shouldAskPermission(permission: String): Boolean { + if (shouldAskPermission()) { + val permissionResult = ActivityCompat.checkSelfPermission(this, permission) + return permissionResult != PackageManager.PERMISSION_GRANTED + } + return false +} \ No newline at end of file diff --git a/library/src/main/java/com/github/javiersantos/piracychecker/utils/SaltUtils.kt b/library/src/main/java/com/github/javiersantos/piracychecker/utils/SaltUtils.kt new file mode 100644 index 0000000..8e0a3c7 --- /dev/null +++ b/library/src/main/java/com/github/javiersantos/piracychecker/utils/SaltUtils.kt @@ -0,0 +1,74 @@ +package com.github.javiersantos.piracychecker.utils + +import android.content.Context +import android.preference.PreferenceManager +import java.util.Random + +/** + * Credits to Aidan Follestad (afollestad) + */ +internal object SaltUtils { + private const val KEY_SALT = "salty-salt" + private var mSalt: ByteArray? = null + + private val saltString: String + get() { + val sb = StringBuilder() + mSalt?.let { + for (i in it.indices) { + if (i > 0) { + sb.append(" ") + } + sb.append(java.lang.Byte.toString(it[i])) + } + } + return sb.toString() + } + + private fun generateSalt(context: Context?) { + mSalt = ByteArray(20) + val randomGenerator = Random() + mSalt?.let { + for (i in 0..19) { + it[i] = (randomGenerator.nextInt(600) - 300).toByte() + } + } + context ?: return + val saltStr = saltString + PreferenceManager.getDefaultSharedPreferences(context) + .edit().putString(KEY_SALT, saltStr) + .apply() + } + + private fun bytesFromString(string: String): ByteArray { + val split = string.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val data = ByteArray(split.size) + for (i in split.indices) { + data[i] = java.lang.Byte.parseByte(split[i]) + } + return data + } + + fun getSalt(context: Context?): ByteArray? { + if (mSalt == null) { + mSalt = context?.let { + try { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + if (prefs.contains( + KEY_SALT)) { + bytesFromString( + prefs.getString( + KEY_SALT, + null)) + } else null + } catch (e: Exception) { + null + } + } + if (mSalt == null) { + generateSalt(context) + } + } + return mSalt + } +} \ No newline at end of file