├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── licenses
│ │ ├── Appcompat
│ │ ├── Constraintlayout
│ │ ├── Material Components for Android
│ │ └── okhttp
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── gr
│ │ └── nikolasspyr
│ │ └── integritycheck
│ │ ├── MainActivity.java
│ │ ├── Utils.java
│ │ └── dialogs
│ │ ├── AboutDialog.java
│ │ └── licenses
│ │ ├── License.java
│ │ ├── LicensesAdapter.java
│ │ ├── LicensesDialog.java
│ │ └── LicensesViewModel.java
│ └── res
│ ├── drawable
│ ├── ic_appicon.xml
│ ├── ic_fail.xml
│ ├── ic_gh.xml
│ ├── ic_help.xml
│ ├── ic_json.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_licenses.xml
│ ├── ic_pass.xml
│ └── ic_unknown.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── dialog_about.xml
│ ├── dialog_licenses.xml
│ └── item_license.xml
│ ├── menu
│ └── menu.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── raw
│ └── isrg_root_x1.pem
│ ├── values-night
│ └── themes.xml
│ ├── values
│ ├── colors.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ ├── data_extraction_rules.xml
│ └── network_security_config.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Local configuration file (sdk path, etc)
6 | local.properties
7 |
8 | # Log/OS Files
9 | *.log
10 |
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild/
14 | .cxx/
15 | *.apk
16 | output.json
17 |
18 | # IntelliJ
19 | *.iml
20 | .idea/
21 | misc.xml
22 | deploymentTargetDropDown.xml
23 | render.experimental.xml
24 |
25 | # Keystore files
26 | *.jks
27 | *.keystore
28 |
29 | # Android Profiling
30 | *.hprof
31 |
32 | # Mac OS
33 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Nikolas Spiridakis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Play Integrity API Checker
4 |
5 | Get info about your Device Integrity through the Play Integrity API
6 |
7 | [
](https://play.google.com/store/apps/details?id=gr.nikolasspyr.integritycheck)
10 |
11 | ## Important Note
12 | If you want to implement the Play Integrity API in your app you shouldn't do it this way. The API server should not send the whole JSON to the app, only a yes/no. Also ideally you should pair the integrity request with another one (for example login). That way your API won't let the user proceed without a valid Integrity token that passes integrity checks (even if your app is reverse engineered).
13 |
14 | ## Setup
15 | In order to run this yourself you'll need:
16 | 1) The [Play Integrity Checker Server](https://github.com/1nikolas/play-integrity-checker-server)
17 | 2) Your server url specified in `local.properties` like this:
18 | ```
19 | API_URL=https://my-awesome-server-url.com
20 | ```
21 | 3) The app to be on Play Store (otherwise you won't have access to `MEETS_BASIC_INTEGRITY` and `MEETS_STRONG_INTEGRITY`)
22 | 4) Play Integrity linked to a Google Cloud project through the Play Console with `MEETS_BASIC_INTEGRITY` and `MEETS_STRONG_INTEGRITY` enabled
23 |
24 | 
25 |
26 | To set up your Google Cloud project see [here](https://github.com/1nikolas/play-integrity-checker-server#how-to-set-up-google-cloud)
27 |
28 | ## License
29 |
30 | MIT License
31 |
32 | ```
33 | Copyright (c) 2022 Nikolas Spiridakis
34 |
35 | Permission is hereby granted, free of charge, to any person obtaining a copy
36 | of this software and associated documentation files (the "Software"), to deal
37 | in the Software without restriction, including without limitation the rights
38 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39 | copies of the Software, and to permit persons to whom the Software is
40 | furnished to do so, subject to the following conditions:
41 |
42 | The above copyright notice and this permission notice shall be included in all
43 | copies or substantial portions of the Software.
44 |
45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
51 | SOFTWARE.
52 | ```
53 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
3 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 |
7 | defaultConfig {
8 | applicationId "gr.nikolasspyr.integritycheck"
9 |
10 | minSdkVersion 21
11 | targetSdkVersion 35
12 | compileSdk 35
13 |
14 | versionCode 21
15 | versionName "2.1"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 |
19 | //Get variables from local.properties
20 | Properties properties = new Properties()
21 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
22 | buildConfigField "String", "API_URL", "\"${properties.getProperty('API_URL')}\""
23 | }
24 |
25 | buildFeatures {
26 | dataBinding true
27 | buildConfig true
28 | }
29 |
30 | buildTypes {
31 | release {
32 | minifyEnabled true
33 | shrinkResources true
34 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
35 | }
36 | }
37 | compileOptions {
38 | sourceCompatibility JavaVersion.VERSION_17
39 | targetCompatibility JavaVersion.VERSION_17
40 | }
41 | namespace 'gr.nikolasspyr.integritycheck'
42 |
43 | }
44 |
45 |
46 | dependencies {
47 |
48 | implementation 'androidx.appcompat:appcompat:1.7.0'
49 | implementation 'com.google.android.material:material:1.12.0'
50 | implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
51 | testImplementation 'junit:junit:4.13.2'
52 | androidTestImplementation 'androidx.test.ext:junit:1.2.1'
53 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
54 |
55 | implementation 'com.google.android.play:integrity:1.4.0'
56 |
57 | implementation 'com.squareup.okhttp3:okhttp:4.12.0'
58 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/assets/licenses/Appcompat:
--------------------------------------------------------------------------------
1 | Copyright (C) 2014 The Android Open Source Project
2 |
3 | https://developer.android.com/jetpack/androidx/releases/appcompat
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
--------------------------------------------------------------------------------
/app/src/main/assets/licenses/Constraintlayout:
--------------------------------------------------------------------------------
1 | Copyright (C) 2015 The Android Open Source Project
2 |
3 | https://developer.android.com/jetpack/androidx/releases/constraintlayout
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
--------------------------------------------------------------------------------
/app/src/main/assets/licenses/Material Components for Android:
--------------------------------------------------------------------------------
1 | Copyright 2018 Google LLC
2 |
3 | https://github.com/material-components/material-components-android
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
--------------------------------------------------------------------------------
/app/src/main/assets/licenses/okhttp:
--------------------------------------------------------------------------------
1 | Copyright 2019 Square, Inc.
2 |
3 | https://github.com/square/okhttp
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/gr/nikolasspyr/integritycheck/MainActivity.java:
--------------------------------------------------------------------------------
1 | package gr.nikolasspyr.integritycheck;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.graphics.Color;
7 | import android.graphics.drawable.Animatable;
8 | import android.graphics.drawable.Drawable;
9 | import android.os.Build;
10 | import android.os.Bundle;
11 | import android.text.TextUtils;
12 | import android.view.Menu;
13 | import android.view.MenuItem;
14 | import android.view.View;
15 | import android.widget.ImageView;
16 | import android.widget.Toast;
17 |
18 | import androidx.annotation.NonNull;
19 | import androidx.appcompat.app.AppCompatActivity;
20 | import androidx.appcompat.widget.Toolbar;
21 | import androidx.constraintlayout.widget.Group;
22 | import androidx.core.content.ContextCompat;
23 | import androidx.core.graphics.Insets;
24 | import androidx.core.view.ViewCompat;
25 | import androidx.core.view.WindowInsetsCompat;
26 |
27 | import com.google.android.gms.tasks.Task;
28 | import com.google.android.material.button.MaterialButton;
29 | import com.google.android.material.color.MaterialColors;
30 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
31 | import com.google.android.material.progressindicator.CircularProgressIndicator;
32 | import com.google.android.play.core.integrity.IntegrityManager;
33 | import com.google.android.play.core.integrity.IntegrityManagerFactory;
34 | import com.google.android.play.core.integrity.IntegrityServiceException;
35 | import com.google.android.play.core.integrity.IntegrityTokenRequest;
36 | import com.google.android.play.core.integrity.IntegrityTokenResponse;
37 | import com.google.android.play.core.integrity.model.IntegrityErrorCode;
38 |
39 | import org.json.JSONObject;
40 |
41 | import java.io.IOException;
42 | import java.util.Locale;
43 |
44 | import gr.nikolasspyr.integritycheck.dialogs.AboutDialog;
45 | import okhttp3.Call;
46 | import okhttp3.Callback;
47 | import okhttp3.OkHttpClient;
48 | import okhttp3.Request;
49 | import okhttp3.Response;
50 | import okhttp3.ResponseBody;
51 |
52 | public class MainActivity extends AppCompatActivity {
53 |
54 | private MaterialButton btn;
55 | private ImageView deviceIntegrityIcon;
56 | private ImageView basicIntegrityIcon;
57 | private ImageView strongIntegrityIcon;
58 | private ImageView virtualIntegrityIcon;
59 |
60 | private Group virtualIntegrity;
61 |
62 | private String jsonResponse;
63 | private Integer[] integrityState = {-1, -1, -1, -1};
64 |
65 | @Override
66 | protected void onCreate(Bundle savedInstanceState) {
67 | super.onCreate(savedInstanceState);
68 | setContentView(R.layout.activity_main);
69 |
70 | Toolbar toolbar = findViewById(R.id.toolbar);
71 | setSupportActionBar(toolbar);
72 |
73 | if (getSupportActionBar() != null) {
74 | getSupportActionBar().setTitle(R.string.app_name);
75 | }
76 |
77 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
78 | ViewCompat.setOnApplyWindowInsetsListener(
79 | findViewById(android.R.id.content),
80 | (view, insets) -> {
81 | Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
82 | view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
83 | return insets;
84 | }
85 | );
86 | }
87 |
88 | btn = findViewById(R.id.check_btn);
89 |
90 | basicIntegrityIcon = findViewById(R.id.basic_integrity_icon);
91 | deviceIntegrityIcon = findViewById(R.id.device_integrity_icon);
92 | strongIntegrityIcon = findViewById(R.id.strong_integrity_icon);
93 | virtualIntegrityIcon = findViewById(R.id.virtual_integrity_icon);
94 |
95 | virtualIntegrity = findViewById(R.id.virtual_integrity);
96 |
97 | btn.setOnClickListener(view -> {
98 | toggleButtonLoading(true);
99 |
100 | jsonResponse = null;
101 | integrityState = new Integer[]{-1, -1, -1, -1};
102 | setIcons(integrityState);
103 |
104 | getToken();
105 | });
106 | }
107 |
108 | private void getToken() {
109 | String nonce = generateNonce();
110 |
111 | // Create an instance of a manager.
112 | IntegrityManager integrityManager = IntegrityManagerFactory.create(getApplicationContext());
113 |
114 | // Request the integrity token by providing a nonce.
115 | Task integrityTokenResponseTask = integrityManager.requestIntegrityToken(
116 | IntegrityTokenRequest.builder()
117 | .setNonce(nonce)
118 | .build());
119 |
120 | integrityTokenResponseTask.addOnSuccessListener(integrityTokenResponse -> sendTokenRequest(integrityTokenResponse.token()));
121 |
122 | integrityTokenResponseTask.addOnFailureListener(e -> {
123 | toggleButtonLoading(false);
124 |
125 | String errorMessage;
126 | if (e instanceof IntegrityServiceException) {
127 | errorMessage = getErrorMessageText((IntegrityServiceException) e);
128 | } else {
129 | errorMessage = e.getMessage();
130 | }
131 |
132 | showErrorDialog(getString(R.string.token_error_title), errorMessage);
133 | });
134 | }
135 |
136 | private void sendTokenRequest(String token) {
137 | OkHttpClient client = new OkHttpClient();
138 | Request request = new Request.Builder()
139 | .get()
140 | .url(BuildConfig.API_URL + "/api/check?token=" + token)
141 | .build();
142 |
143 | client.newCall(request).enqueue(new Callback() {
144 | @Override
145 | public void onFailure(@NonNull Call call, @NonNull IOException e) {
146 | onRequestError(e.getMessage());
147 | }
148 |
149 | @Override
150 | public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
151 | if (!response.isSuccessful()) {
152 | onRequestError(String.format(Locale.US, getString(R.string.server_api_error_status_code), response.code()));
153 | return;
154 | }
155 |
156 | ResponseBody responseBody = response.body();
157 |
158 | if (responseBody == null) {
159 | onRequestError(getString(R.string.server_api_error_empty_res));
160 | return;
161 | }
162 |
163 | String responseBodyString = responseBody.string();
164 |
165 | try {
166 | parseResponseJSON(responseBodyString);
167 | } catch (Exception e) {
168 | onRequestError(e.getMessage());
169 | }
170 |
171 | }
172 | });
173 | }
174 |
175 | private void onRequestError(String error) {
176 | runOnUiThread(() -> {
177 | showErrorDialog(getString(R.string.server_api_error_title), error);
178 | toggleButtonLoading(false);
179 | });
180 | }
181 |
182 | private void parseResponseJSON(String apiResponseJson) throws Exception {
183 | JSONObject json = new JSONObject(apiResponseJson);
184 |
185 | if (json.has("error")) {
186 | throw new Exception(json.getString("error"));
187 | }
188 |
189 | JSONObject result;
190 |
191 | if (json.has("deviceIntegrity")) {
192 | result = json.getJSONObject("deviceIntegrity");
193 | jsonResponse = json.toString(4);
194 | } else {
195 | result = new JSONObject();
196 | }
197 |
198 | if (result.has("deviceRecognitionVerdict")) {
199 | integrityState = parseValues(result.get("deviceRecognitionVerdict").toString());
200 |
201 | } else {
202 | integrityState = parseValues("");
203 | }
204 |
205 | runOnUiThread(() -> setIcons(integrityState));
206 |
207 | runOnUiThread(() -> toggleButtonLoading(false));
208 | }
209 |
210 |
211 | private void toggleButtonLoading(boolean isLoading) {
212 | setButtonLoading(btn, isLoading);
213 | btn.setEnabled(!isLoading);
214 | }
215 |
216 | private Drawable getProgressBarDrawable(Context context) {
217 | CircularProgressIndicator drawable = new CircularProgressIndicator(context);
218 | drawable.setIndicatorSize(48);
219 | drawable.setTrackThickness(5);
220 | drawable.setIndicatorColor(MaterialColors.getColor(context, com.google.android.material.R.attr.colorSecondary, Color.BLUE));
221 | drawable.setIndeterminate(true);
222 | return drawable.getIndeterminateDrawable();
223 | }
224 |
225 | private void setButtonLoading(MaterialButton button, boolean loading) {
226 | button.setMaxLines(1);
227 | button.setEllipsize(TextUtils.TruncateAt.END);
228 | button.setIconGravity(MaterialButton.ICON_GRAVITY_START);
229 |
230 | if (loading) {
231 | Drawable drawable = button.getIcon();
232 | if (!(drawable instanceof Animatable)) {
233 | drawable = getProgressBarDrawable(button.getContext());
234 | if (drawable instanceof Animatable) {
235 | button.setIcon(drawable);
236 | ((Animatable) drawable).start();
237 | }
238 | }
239 | } else {
240 | button.setIcon(null);
241 | }
242 | }
243 |
244 | private String generateNonce() {
245 | int length = 50;
246 | String nonce = "";
247 | String allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
248 | for (int i = 0; i < length; i++) {
249 | nonce = nonce.concat(String.valueOf(allowed.charAt((int) Math.floor(Math.random() * allowed.length()))));
250 | }
251 | return nonce;
252 | }
253 |
254 | private void showErrorDialog(String title, String message) {
255 | new MaterialAlertDialogBuilder(MainActivity.this, R.style.Theme_PlayIntegrityAPIChecker_Dialogs)
256 | .setTitle(title)
257 | .setCancelable(true)
258 | .setPositiveButton(R.string.ok, (dialogInterface, i) -> {
259 |
260 | })
261 | .setMessage(message)
262 | .show();
263 | }
264 |
265 | private Integer[] parseValues(String integrity) {
266 | return new Integer[]{integrity.contains("MEETS_BASIC_INTEGRITY") ? 1 : 0, integrity.contains("MEETS_DEVICE_INTEGRITY") ? 1 : 0, integrity.contains("MEETS_STRONG_INTEGRITY") ? 1 : 0, integrity.contains("MEETS_VIRTUAL_INTEGRITY") ? 1 : -1};
267 | }
268 |
269 |
270 | private void setIcons(Integer[] integrityState) {
271 | setIcon(basicIntegrityIcon, integrityState[0]);
272 | setIcon(deviceIntegrityIcon, integrityState[1]);
273 | setIcon(strongIntegrityIcon, integrityState[2]);
274 | setIcon(virtualIntegrityIcon, integrityState[3]);
275 |
276 | if (integrityState[3] != -1) {
277 | virtualIntegrity.setVisibility(View.VISIBLE);
278 | } else {
279 | virtualIntegrity.setVisibility(View.GONE);
280 | }
281 | }
282 |
283 | private void setIcon(ImageView img, int state) {
284 | if (state == -1) {
285 | img.setImageDrawable(ContextCompat.getDrawable(MainActivity.this, R.drawable.ic_unknown));
286 | img.setContentDescription(getString(R.string.status_unknown));
287 | } else if (state == 0) {
288 | img.setImageDrawable(ContextCompat.getDrawable(MainActivity.this, R.drawable.ic_fail));
289 | img.setContentDescription(getString(R.string.status_fail));
290 | } else {
291 | img.setImageDrawable(ContextCompat.getDrawable(MainActivity.this, R.drawable.ic_pass));
292 | img.setContentDescription(getString(R.string.status_pass));
293 | }
294 | }
295 |
296 | private String getErrorCodeName(int errorCode) {
297 | return switch (errorCode) {
298 | case IntegrityErrorCode.NO_ERROR -> "NO_ERROR";
299 | case IntegrityErrorCode.API_NOT_AVAILABLE -> "API_NOT_AVAILABLE";
300 | case IntegrityErrorCode.PLAY_STORE_NOT_FOUND -> "PLAY_STORE_NOT_FOUND";
301 | case IntegrityErrorCode.NETWORK_ERROR -> "NETWORK_ERROR";
302 | case IntegrityErrorCode.PLAY_STORE_ACCOUNT_NOT_FOUND -> "PLAY_STORE_ACCOUNT_NOT_FOUND";
303 | case IntegrityErrorCode.APP_NOT_INSTALLED -> "APP_NOT_INSTALLED";
304 | case IntegrityErrorCode.PLAY_SERVICES_NOT_FOUND -> "PLAY_SERVICES_NOT_FOUND";
305 | case IntegrityErrorCode.APP_UID_MISMATCH -> "APP_UID_MISMATCH";
306 | case IntegrityErrorCode.TOO_MANY_REQUESTS -> "TOO_MANY_REQUESTS";
307 | case IntegrityErrorCode.CANNOT_BIND_TO_SERVICE -> "CANNOT_BIND_TO_SERVICE";
308 | case IntegrityErrorCode.NONCE_TOO_SHORT -> "NONCE_TOO_SHORT";
309 | case IntegrityErrorCode.NONCE_TOO_LONG -> "NONCE_TOO_LONG";
310 | case IntegrityErrorCode.GOOGLE_SERVER_UNAVAILABLE -> "GOOGLE_SERVER_UNAVAILABLE";
311 | case IntegrityErrorCode.NONCE_IS_NOT_BASE64 -> "NONCE_IS_NOT_BASE64";
312 | case IntegrityErrorCode.PLAY_STORE_VERSION_OUTDATED -> "PLAY_STORE_VERSION_OUTDATED";
313 | case IntegrityErrorCode.PLAY_SERVICES_VERSION_OUTDATED ->
314 | "PLAY_SERVICES_VERSION_OUTDATED";
315 | case IntegrityErrorCode.CLOUD_PROJECT_NUMBER_IS_INVALID ->
316 | "CLOUD_PROJECT_NUMBER_IS_INVALID";
317 | case IntegrityErrorCode.CLIENT_TRANSIENT_ERROR -> "CLIENT_TRANSIENT_ERROR";
318 | case IntegrityErrorCode.INTERNAL_ERROR -> "INTERNAL_ERROR";
319 | default -> "UNKNOWN_ERROR_CODE";
320 | };
321 | }
322 |
323 | private String getErrorReason(int errorCode) {
324 | return switch (errorCode) {
325 | case IntegrityErrorCode.API_NOT_AVAILABLE ->
326 | getString(R.string.error_reason_api_not_available);
327 | case IntegrityErrorCode.APP_NOT_INSTALLED ->
328 | getString(R.string.error_reason_app_not_installed);
329 | case IntegrityErrorCode.APP_UID_MISMATCH ->
330 | getString(R.string.error_reason_app_uid_mismatch);
331 | case IntegrityErrorCode.CANNOT_BIND_TO_SERVICE ->
332 | getString(R.string.error_reason_cannot_bind_to_service);
333 | case IntegrityErrorCode.CLIENT_TRANSIENT_ERROR ->
334 | getString(R.string.error_reason_client_transient_error);
335 | case IntegrityErrorCode.CLOUD_PROJECT_NUMBER_IS_INVALID ->
336 | getString(R.string.error_reason_cloud_project_number_is_invalid);
337 | case IntegrityErrorCode.GOOGLE_SERVER_UNAVAILABLE ->
338 | getString(R.string.error_reason_google_server_unavailable);
339 | case IntegrityErrorCode.INTERNAL_ERROR ->
340 | getString(R.string.error_reason_internal_error);
341 | case IntegrityErrorCode.NETWORK_ERROR -> getString(R.string.error_reason_network_error);
342 | case IntegrityErrorCode.NONCE_IS_NOT_BASE64 ->
343 | getString(R.string.error_reason_nonce_is_not_base64);
344 | case IntegrityErrorCode.NONCE_TOO_LONG ->
345 | getString(R.string.error_reason_nonce_too_long);
346 | case IntegrityErrorCode.NONCE_TOO_SHORT ->
347 | getString(R.string.error_reason_nonce_too_short);
348 | case IntegrityErrorCode.NO_ERROR -> "";
349 | case IntegrityErrorCode.PLAY_SERVICES_NOT_FOUND ->
350 | getString(R.string.error_reason_play_services_not_found);
351 | case IntegrityErrorCode.PLAY_SERVICES_VERSION_OUTDATED ->
352 | getString(R.string.error_reason_play_services_outdated);
353 | case IntegrityErrorCode.PLAY_STORE_ACCOUNT_NOT_FOUND ->
354 | getString(R.string.error_reason_play_store_account_not_found);
355 | case IntegrityErrorCode.PLAY_STORE_NOT_FOUND ->
356 | getString(R.string.error_reason_play_store_not_found);
357 | case IntegrityErrorCode.PLAY_STORE_VERSION_OUTDATED ->
358 | getString(R.string.error_reason_play_store_version_outdated);
359 | case IntegrityErrorCode.TOO_MANY_REQUESTS ->
360 | getString(R.string.error_reason_too_many_requests);
361 |
362 | default -> getString(R.string.error_reason_unknown);
363 | };
364 | }
365 |
366 | private String getErrorSolution(int errorCode) {
367 | return switch (errorCode) {
368 | case IntegrityErrorCode.API_NOT_AVAILABLE, IntegrityErrorCode.CANNOT_BIND_TO_SERVICE,
369 | IntegrityErrorCode.PLAY_STORE_VERSION_OUTDATED ->
370 | getString(R.string.error_solution_update_play_store);
371 | case IntegrityErrorCode.APP_NOT_INSTALLED, IntegrityErrorCode.APP_UID_MISMATCH ->
372 | getString(R.string.error_solution_something_wrong_attack);
373 | case IntegrityErrorCode.CLIENT_TRANSIENT_ERROR,
374 | IntegrityErrorCode.GOOGLE_SERVER_UNAVAILABLE, IntegrityErrorCode.INTERNAL_ERROR ->
375 | getString(R.string.error_solution_try_again);
376 | case IntegrityErrorCode.CLOUD_PROJECT_NUMBER_IS_INVALID,
377 | IntegrityErrorCode.NONCE_IS_NOT_BASE64, IntegrityErrorCode.NONCE_TOO_LONG,
378 | IntegrityErrorCode.NONCE_TOO_SHORT, IntegrityErrorCode.NO_ERROR ->
379 | getString(R.string.error_solution_open_issue);
380 | case IntegrityErrorCode.NETWORK_ERROR ->
381 | getString(R.string.error_solution_check_connection);
382 | case IntegrityErrorCode.PLAY_SERVICES_NOT_FOUND ->
383 | getString(R.string.error_solution_install_update_play_services);
384 | case IntegrityErrorCode.PLAY_SERVICES_VERSION_OUTDATED ->
385 | getString(R.string.error_solution_update_play_services);
386 | case IntegrityErrorCode.PLAY_STORE_ACCOUNT_NOT_FOUND ->
387 | getString(R.string.error_solution_login);
388 | case IntegrityErrorCode.PLAY_STORE_NOT_FOUND ->
389 | getString(R.string.error_solution_install_official_play_store);
390 | case IntegrityErrorCode.TOO_MANY_REQUESTS ->
391 | getString(R.string.error_solution_try_again_later);
392 |
393 | default -> "";
394 | };
395 | }
396 |
397 | private String getErrorMessageText(IntegrityServiceException integrityServiceException) {
398 | int errorCode = integrityServiceException.getErrorCode();
399 |
400 | StringBuilder errorMessageBuilder = new StringBuilder();
401 | errorMessageBuilder.append(String.format(Locale.US, "%s (%d)", getErrorCodeName(errorCode), errorCode));
402 |
403 | String errorReason = getErrorReason(errorCode);
404 | if (!errorReason.isEmpty()) {
405 | errorMessageBuilder.append("\n");
406 | errorMessageBuilder.append(errorReason);
407 | }
408 |
409 | String errorSolution = getErrorSolution(errorCode);
410 | if (!errorSolution.isEmpty()) {
411 | errorMessageBuilder.append("\n\n");
412 | errorMessageBuilder.append(errorSolution);
413 | }
414 |
415 | return errorMessageBuilder.toString();
416 | }
417 |
418 | // Menu stuff
419 | @Override
420 | public boolean onCreateOptionsMenu(@NonNull Menu menu) {
421 | getMenuInflater().inflate(R.menu.menu, menu);
422 |
423 | return true;
424 | }
425 |
426 | @Override
427 | public boolean onOptionsItemSelected(MenuItem item) {
428 | int id = item.getItemId();
429 |
430 | if (id == R.id.about) {
431 | new AboutDialog(MainActivity.this).show();
432 | return true;
433 | } else if (id == R.id.json_response) {
434 | if (jsonResponse == null) {
435 | Toast.makeText(this, R.string.check_first, Toast.LENGTH_SHORT).show();
436 | } else {
437 | new MaterialAlertDialogBuilder(MainActivity.this, R.style.Theme_PlayIntegrityAPIChecker_Dialogs)
438 | .setTitle(R.string.json_response)
439 | .setCancelable(true)
440 | .setPositiveButton(R.string.ok, null)
441 | .setNeutralButton(R.string.copy_json, (dialogInterface, i) -> {
442 | ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
443 | ClipData clip = ClipData.newPlainText("", jsonResponse);
444 | clipboard.setPrimaryClip(clip);
445 | dialogInterface.dismiss();
446 | Toast.makeText(MainActivity.this, getString(R.string.copied), Toast.LENGTH_SHORT).show();
447 | })
448 | .setMessage(jsonResponse)
449 | .show();
450 | }
451 | return true;
452 | } else if (id == R.id.documentation) {
453 | Utils.openLink(getString(R.string.docs_link), this);
454 | return true;
455 | } else {
456 | return super.onOptionsItemSelected(item);
457 | }
458 |
459 | }
460 |
461 |
462 | }
--------------------------------------------------------------------------------
/app/src/main/java/gr/nikolasspyr/integritycheck/Utils.java:
--------------------------------------------------------------------------------
1 | package gr.nikolasspyr.integritycheck;
2 |
3 | import android.content.ActivityNotFoundException;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 | import android.widget.Toast;
8 |
9 | public class Utils {
10 | public static void openLink(String url, Context context) {
11 | Intent i = new Intent(Intent.ACTION_VIEW);
12 | i.setData(Uri.parse(url));
13 | try {
14 | context.startActivity(i);
15 | } catch (ActivityNotFoundException e) { //For devices that have no browsers
16 | Toast.makeText(context, R.string.no_browser_found, Toast.LENGTH_LONG).show();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/nikolasspyr/integritycheck/dialogs/AboutDialog.java:
--------------------------------------------------------------------------------
1 | package gr.nikolasspyr.integritycheck.dialogs;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 | import android.widget.TextView;
6 |
7 | import androidx.appcompat.app.AlertDialog;
8 |
9 | import com.google.android.material.button.MaterialButton;
10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
11 |
12 | import java.util.Locale;
13 |
14 | import gr.nikolasspyr.integritycheck.BuildConfig;
15 | import gr.nikolasspyr.integritycheck.R;
16 | import gr.nikolasspyr.integritycheck.Utils;
17 | import gr.nikolasspyr.integritycheck.dialogs.licenses.LicensesDialog;
18 |
19 |
20 | public class AboutDialog {
21 |
22 | private final AlertDialog dialog;
23 |
24 | public AboutDialog(Context context) {
25 |
26 | dialog = new MaterialAlertDialogBuilder(context, R.style.Theme_PlayIntegrityAPIChecker_Dialogs)
27 | .setPositiveButton(R.string.ok, (dialog, which) -> {
28 |
29 | })
30 | .create();
31 |
32 | View dialogView = View.inflate(context, R.layout.dialog_about, null);
33 |
34 | dialog.setView(dialogView);
35 |
36 | TextView aboutText = dialogView.findViewById(R.id.about_text);
37 |
38 | aboutText.setText(String.format(Locale.US, context.getResources().getString(R.string.about_text), BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
39 |
40 | MaterialButton githubBtn = dialogView.findViewById(R.id.about_github);
41 | MaterialButton licensesBtn = dialogView.findViewById(R.id.about_licenses);
42 |
43 |
44 | githubBtn.setOnClickListener(v -> {
45 | Utils.openLink(context.getString(R.string.about_github_link), context);
46 | dialog.dismiss();
47 | });
48 |
49 |
50 | licensesBtn.setOnClickListener(v -> {
51 | new LicensesDialog(context).show();
52 | dialog.dismiss();
53 | });
54 |
55 | }
56 |
57 | public void show() {
58 | dialog.show();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/nikolasspyr/integritycheck/dialogs/licenses/License.java:
--------------------------------------------------------------------------------
1 | package gr.nikolasspyr.integritycheck.dialogs.licenses;
2 |
3 | public class License {
4 |
5 | public String subject;
6 | public String text;
7 |
8 | public License(String subject, String text) {
9 | this.subject = subject;
10 | this.text = text;
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/nikolasspyr/integritycheck/dialogs/licenses/LicensesAdapter.java:
--------------------------------------------------------------------------------
1 | package gr.nikolasspyr.integritycheck.dialogs.licenses;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import java.util.List;
13 |
14 | import gr.nikolasspyr.integritycheck.R;
15 |
16 |
17 | public class LicensesAdapter extends RecyclerView.Adapter {
18 |
19 | private final LayoutInflater mInflater;
20 | private List mLicenses;
21 |
22 | public LicensesAdapter(Context c) {
23 | mInflater = LayoutInflater.from(c);
24 | }
25 |
26 | public void setLicenses(List licenses) {
27 | mLicenses = licenses;
28 | notifyItemRangeInserted(0, licenses.size());
29 | }
30 |
31 | @NonNull
32 | @Override
33 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
34 | return new ViewHolder(mInflater.inflate(R.layout.item_license, parent, false));
35 | }
36 |
37 | @Override
38 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
39 | holder.bind(mLicenses.get(position));
40 | }
41 |
42 | @Override
43 | public int getItemCount() {
44 | return mLicenses == null ? 0 : mLicenses.size();
45 | }
46 |
47 | static class ViewHolder extends RecyclerView.ViewHolder {
48 |
49 | private final TextView title;
50 | private final TextView text;
51 |
52 | private ViewHolder(@NonNull View itemView) {
53 | super(itemView);
54 |
55 | title = itemView.findViewById(R.id.license_title);
56 | text = itemView.findViewById(R.id.license_text);
57 | }
58 |
59 | private void bind(License license) {
60 | title.setText(license.subject);
61 | text.setText(license.text);
62 | }
63 | }
64 |
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/nikolasspyr/integritycheck/dialogs/licenses/LicensesDialog.java:
--------------------------------------------------------------------------------
1 | package gr.nikolasspyr.integritycheck.dialogs.licenses;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 |
6 | import androidx.appcompat.app.AlertDialog;
7 | import androidx.appcompat.app.AppCompatActivity;
8 | import androidx.recyclerview.widget.LinearLayoutManager;
9 | import androidx.recyclerview.widget.RecyclerView;
10 |
11 | import com.google.android.material.button.MaterialButton;
12 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
13 |
14 | import gr.nikolasspyr.integritycheck.R;
15 |
16 |
17 | public class LicensesDialog {
18 |
19 | private final AlertDialog dialog;
20 | private final Context context;
21 |
22 | public LicensesDialog(Context context) {
23 | this.context = context;
24 |
25 | dialog = new MaterialAlertDialogBuilder(context, R.style.Theme_PlayIntegrityAPIChecker_Dialogs)
26 | .setTitle(R.string.licenses)
27 | .setPositiveButton(R.string.ok, (dialog, which) -> {
28 |
29 | })
30 | .create();
31 |
32 | View dialogView = View.inflate(context, R.layout.dialog_licenses, null);
33 |
34 | dialog.setView(dialogView);
35 |
36 | RecyclerView recyclerView = dialogView.findViewById(R.id.licenses_recycler);
37 |
38 | recyclerView.setLayoutManager(new LinearLayoutManager(context));
39 | recyclerView.getRecycledViewPool().setMaxRecycledViews(0, 16);
40 |
41 | LicensesAdapter adapter = new LicensesAdapter(context);
42 | recyclerView.setAdapter(adapter);
43 |
44 | AppCompatActivity activity = (AppCompatActivity) context;
45 |
46 | LicensesViewModel viewModel = new LicensesViewModel(activity.getApplication());
47 | viewModel.getLicenses().observe(activity, adapter::setLicenses);
48 |
49 | }
50 |
51 | public void show() {
52 | dialog.show();
53 |
54 | MaterialButton button = (MaterialButton) dialog.getButton(AlertDialog.BUTTON_POSITIVE);
55 |
56 | button.setTextColor(context.getResources().getColor(R.color.blueSecondary));
57 |
58 | button.setRippleColorResource(R.color.blueSecondary);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/gr/nikolasspyr/integritycheck/dialogs/licenses/LicensesViewModel.java:
--------------------------------------------------------------------------------
1 | package gr.nikolasspyr.integritycheck.dialogs.licenses;
2 |
3 | import android.app.Application;
4 | import android.content.res.AssetManager;
5 |
6 | import androidx.annotation.NonNull;
7 | import androidx.lifecycle.AndroidViewModel;
8 | import androidx.lifecycle.LiveData;
9 | import androidx.lifecycle.MutableLiveData;
10 |
11 | import java.io.ByteArrayOutputStream;
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.io.OutputStream;
15 | import java.util.ArrayList;
16 | import java.util.Collections;
17 | import java.util.List;
18 |
19 | public class LicensesViewModel extends AndroidViewModel {
20 |
21 | private final MutableLiveData> mLicenses = new MutableLiveData<>();
22 | private final MutableLiveData mAreLicensesLoading = new MutableLiveData<>();
23 |
24 | public LicensesViewModel(@NonNull Application application) {
25 | super(application);
26 |
27 | mLicenses.setValue(Collections.emptyList());
28 | mAreLicensesLoading.setValue(false);
29 |
30 | loadLicences();
31 | }
32 |
33 | public LiveData> getLicenses() {
34 | return mLicenses;
35 | }
36 |
37 | private void loadLicences() {
38 | if (Boolean.TRUE.equals(mAreLicensesLoading.getValue()))
39 | return;
40 |
41 | mAreLicensesLoading.setValue(true);
42 |
43 | new Thread(() -> {
44 | try {
45 | AssetManager assetManager = getApplication().getAssets();
46 | String licensesDir = "licenses";
47 |
48 | String[] rawLicenses = assetManager.list(licensesDir);
49 | assert rawLicenses != null;
50 | ArrayList licenses = new ArrayList<>(rawLicenses.length);
51 |
52 | for (String rawLicense : rawLicenses)
53 | licenses.add(new License(rawLicense, readStream(assetManager.open(licensesDir + "/" + rawLicense), "UTF-8")));
54 |
55 | Collections.sort(licenses, (license1, license2) -> license1.subject.compareToIgnoreCase(license2.subject));
56 |
57 | mLicenses.postValue(licenses);
58 | mAreLicensesLoading.postValue(false);
59 | } catch (Exception e) {
60 | mAreLicensesLoading.postValue(false);
61 | }
62 | }).start();
63 | }
64 |
65 | public static void copyStream(InputStream from, OutputStream to) throws IOException {
66 | byte[] buf = new byte[1024 * 1024];
67 | int len;
68 | while ((len = from.read(buf)) > 0) {
69 | to.write(buf, 0, len);
70 | }
71 | }
72 |
73 | public static byte[] readStream(InputStream inputStream) throws IOException {
74 | try (InputStream in = inputStream) {
75 | return readStreamNoClose(in);
76 | }
77 | }
78 |
79 | public static String readStream(InputStream inputStream, String charset) throws IOException {
80 | return new String(readStream(inputStream), charset);
81 | }
82 |
83 | public static byte[] readStreamNoClose(InputStream inputStream) throws IOException {
84 | ByteArrayOutputStream buffer = new ByteArrayOutputStream();
85 | copyStream(inputStream, buffer);
86 | return buffer.toByteArray();
87 | }
88 |
89 |
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_appicon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_fail.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_gh.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
18 |
24 |
30 |
36 |
42 |
48 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_help.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_json.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_licenses.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pass.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_unknown.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
21 |
22 |
26 |
27 |
30 |
31 |
38 |
39 |
53 |
54 |
61 |
62 |
69 |
70 |
77 |
78 |
85 |
86 |
87 |
100 |
101 |
114 |
115 |
128 |
129 |
142 |
143 |
144 |
151 |
152 |
153 |
167 |
168 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
30 |
31 |
32 |
44 |
45 |
51 |
52 |
62 |
63 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_licenses.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_license.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/isrg_root_x1.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5 | WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6 | ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7 | MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8 | h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9 | 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10 | A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11 | T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12 | B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13 | B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14 | KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15 | OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16 | jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17 | qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18 | rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19 | HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20 | hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21 | ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22 | 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23 | NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24 | ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25 | TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26 | jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27 | oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28 | 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29 | mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30 | emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31 | -----END CERTIFICATE-----
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
23 |
24 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 | #ffffff
5 | #DE000000
6 | #DEFFFFFF
7 | #4285f4
8 | #ECF3FE
9 | #171E29
10 | #0059c1
11 | #121212
12 | #3ec714
13 | #c91818
14 | #FFDA1A
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #353937
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Play Integrity API Checker
3 | Error
4 | OK
5 | Check
6 | Integrity Checker
7 | Licenses
8 | Github
9 | Made by Nikolas Spiridakis\nVersion: %s (%d)
10 | About
11 | https://github.com/1nikolas/play-integrity-checker-app
12 | No browser found!
13 | Unknown
14 | Pass
15 | Fail
16 | Show JSON response
17 | Raw JSON response
18 | Please run a check first!
19 | Learn what the results mean
20 | https://developer.android.com/google/play/integrity/verdicts#optional-device-labels
21 | Copy JSON
22 | Copied to clipboard!
23 | Integrity API Error
24 | Server API Request Error
25 | API returned: %d
26 | Empty Response
27 |
28 |
29 | Integrity API is not available.
30 | The calling app is not installed.
31 | The calling app UID (user id) does not match the one from Package Manager.
32 | Binding to the service in the Play Store has failed. This can be due to having an old Play Store version installed on the device.
33 | There was a transient error in the client device.
34 | The provided cloud project number is invalid.
35 | Unknown internal Google server error.
36 | Unknown internal error.
37 | No available network is found.
38 | Nonce is not encoded as a base64 web-safe no-wrap string.
39 | Nonce length is too long. The nonce must be less than 500 bytes before base64 encoding.
40 | Nonce length is too short. The nonce must be a minimum of 16 bytes (before base64 encoding) to allow for a better security.
41 | Play Services is not available or version is too old.
42 | Play Services needs to be updated.
43 | No Play Store account is found on device.
44 | No Play Store app is found on device or not official version is installed.
45 | The Play Store needs to be updated.
46 | The calling app is making too many requests to the API and hence is throttled.
47 | Unknown error.
48 |
49 |
50 | Try updating Play Store.
51 | Something is wrong (possibly an attack).
52 | Try again.
53 | If this happens consistently, open an issue on Github.
54 | Check your connection.
55 | Try installing/updating Google Play Services.
56 | Try updating Google Play Services.
57 | Try logging in to Play Store.
58 | Try installing an official and recent version of Play Store.
59 | Try again later.
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
23 |
27 |
28 |
32 |
33 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '8.10.1' apply false
4 | id 'com.android.library' version '8.10.1' apply false
5 | }
6 |
7 | tasks.register('clean', Delete) {
8 | delete layout.buildDirectory
9 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
22 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1nikolas/play-integrity-checker-app/4240c4eae9a6badbc9b421d00913b0bb44483934/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jan 16 16:08:38 EET 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 | ' "$PWD" ) || exit
91 |
92 | # Use the maximum available, or set MAX_FD != -1 to use that value.
93 | MAX_FD=maximum
94 |
95 | warn () {
96 | echo "$*"
97 | } >&2
98 |
99 | die () {
100 | echo
101 | echo "$*"
102 | echo
103 | exit 1
104 | } >&2
105 |
106 | # OS specific support (must be 'true' or 'false').
107 | cygwin=false
108 | msys=false
109 | darwin=false
110 | nonstop=false
111 | case "$( uname )" in #(
112 | CYGWIN* ) cygwin=true ;; #(
113 | Darwin* ) darwin=true ;; #(
114 | MSYS* | MINGW* ) msys=true ;; #(
115 | NONSTOP* ) nonstop=true ;;
116 | esac
117 |
118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119 |
120 |
121 | # Determine the Java command to use to start the JVM.
122 | if [ -n "$JAVA_HOME" ] ; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD=$JAVA_HOME/jre/sh/java
126 | else
127 | JAVACMD=$JAVA_HOME/bin/java
128 | fi
129 | if [ ! -x "$JAVACMD" ] ; then
130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
131 |
132 | Please set the JAVA_HOME variable in your environment to match the
133 | location of your Java installation."
134 | fi
135 | else
136 | JAVACMD=java
137 | if ! command -v java >/dev/null 2>&1
138 | then
139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
140 |
141 | Please set the JAVA_HOME variable in your environment to match the
142 | location of your Java installation."
143 | fi
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
148 | case $MAX_FD in #(
149 | max*)
150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
151 | # shellcheck disable=SC2039,SC3045
152 | MAX_FD=$( ulimit -H -n ) ||
153 | warn "Could not query maximum file descriptor limit"
154 | esac
155 | case $MAX_FD in #(
156 | '' | soft) :;; #(
157 | *)
158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
159 | # shellcheck disable=SC2039,SC3045
160 | ulimit -n "$MAX_FD" ||
161 | warn "Could not set maximum file descriptor limit to $MAX_FD"
162 | esac
163 | fi
164 |
165 | # Collect all arguments for the java command, stacking in reverse order:
166 | # * args from the command line
167 | # * the main class name
168 | # * -classpath
169 | # * -D...appname settings
170 | # * --module-path (only if needed)
171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
172 |
173 | # For Cygwin or MSYS, switch paths to Windows format before running java
174 | if "$cygwin" || "$msys" ; then
175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177 |
178 | JAVACMD=$( cygpath --unix "$JAVACMD" )
179 |
180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
181 | for arg do
182 | if
183 | case $arg in #(
184 | -*) false ;; # don't mess with options #(
185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
186 | [ -e "$t" ] ;; #(
187 | *) false ;;
188 | esac
189 | then
190 | arg=$( cygpath --path --ignore --mixed "$arg" )
191 | fi
192 | # Roll the args list around exactly as many times as the number of
193 | # args, so each arg winds up back in the position where it started, but
194 | # possibly modified.
195 | #
196 | # NB: a `for` loop captures its iteration list before it begins, so
197 | # changing the positional parameters here affects neither the number of
198 | # iterations, nor the values presented in `arg`.
199 | shift # remove old arg
200 | set -- "$@" "$arg" # push replacement arg
201 | done
202 | fi
203 |
204 |
205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207 |
208 | # Collect all arguments for the java command:
209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210 | # and any embedded shellness will be escaped.
211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212 | # treated as '${Hostname}' itself on the command line.
213 |
214 | set -- \
215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
216 | -classpath "$CLASSPATH" \
217 | org.gradle.wrapper.GradleWrapperMain \
218 | "$@"
219 |
220 | # Stop when "xargs" is not available.
221 | if ! command -v xargs >/dev/null 2>&1
222 | then
223 | die "xargs is not available"
224 | fi
225 |
226 | # Use "xargs" to parse quoted args.
227 | #
228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
229 | #
230 | # In Bash we could simply go:
231 | #
232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
233 | # set -- "${ARGS[@]}" "$@"
234 | #
235 | # but POSIX shell has neither arrays nor command substitution, so instead we
236 | # post-process each arg (as a line of input to sed) to backslash-escape any
237 | # character that might be a shell metacharacter, then use eval to reverse
238 | # that process (while maintaining the separation between arguments), and wrap
239 | # the whole thing up as a single "set" statement.
240 | #
241 | # This will of course break if any of these variables contains a newline or
242 | # an unmatched quote.
243 | #
244 |
245 | eval "set -- $(
246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
247 | xargs -n1 |
248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
249 | tr '\n' ' '
250 | )" '"$@"'
251 |
252 | exec "$JAVACMD" "$@"
253 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Play Integrity API Checker"
16 | include ':app'
17 |
--------------------------------------------------------------------------------