From 61957c33cd2086ffffa65869ffd46a8e7ae1181b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 1 Feb 2022 13:24:54 +0100 Subject: [PATCH 01/26] ProfileManager: Allow overriding User-Agent in WebView --- .../gms/firebase/auth/ReCaptchaActivity.kt | 4 ++++ .../gms/firebase/auth/ReCaptchaOverlay.kt | 9 ++++++--- .../kotlin/org/microg/gms/profile/Build.kt | 12 ++++++++---- .../microg/gms/auth/login/LoginActivity.java | 18 ++++++++++-------- .../microg/gms/safetynet/ReCaptchaActivity.kt | 4 ++++ 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/firebase-auth-core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaActivity.kt b/firebase-auth-core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaActivity.kt index 784d4298a..185540377 100644 --- a/firebase-auth-core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaActivity.kt +++ b/firebase-auth-core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaActivity.kt @@ -18,6 +18,8 @@ import android.webkit.WebSettings import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity import org.microg.gms.firebase.auth.core.R +import org.microg.gms.profile.Build +import org.microg.gms.profile.ProfileManager import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine @@ -47,6 +49,8 @@ class ReCaptchaActivity : AppCompatActivity() { settings.setSupportZoom(false) settings.displayZoomControls = false settings.cacheMode = WebSettings.LOAD_NO_CACHE + ProfileManager.ensureInitialized(this) + settings.userAgentString = Build.generateWebViewUserAgentString(settings.userAgentString) view.addJavascriptInterface(object : Any() { @JavascriptInterface fun onReCaptchaToken(token: String) { diff --git a/firebase-auth-core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlay.kt b/firebase-auth-core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlay.kt index 5af19c687..03da115c0 100644 --- a/firebase-auth-core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlay.kt +++ b/firebase-auth-core/src/main/kotlin/org/microg/gms/firebase/auth/ReCaptchaOverlay.kt @@ -7,7 +7,6 @@ package org.microg.gms.firebase.auth import android.content.Context import android.graphics.PixelFormat -import android.os.Build import android.provider.Settings import android.util.DisplayMetrics import android.util.Log @@ -17,6 +16,8 @@ import android.webkit.WebSettings import android.webkit.WebView import android.widget.FrameLayout import org.microg.gms.firebase.auth.core.R +import org.microg.gms.profile.Build +import org.microg.gms.profile.ProfileManager import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -32,7 +33,7 @@ class ReCaptchaOverlay(val context: Context, val apiKey: String, val hostname: S var container: View? = null private fun show() { - val layoutParamsType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val layoutParamsType = if (android.os.Build.VERSION.SDK_INT >= 26) { WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { WindowManager.LayoutParams.TYPE_SYSTEM_ALERT @@ -80,6 +81,8 @@ class ReCaptchaOverlay(val context: Context, val apiKey: String, val hostname: S settings.setSupportZoom(false) settings.displayZoomControls = false settings.cacheMode = WebSettings.LOAD_NO_CACHE + ProfileManager.ensureInitialized(context) + settings.userAgentString = Build.generateWebViewUserAgentString(settings.userAgentString) view.addJavascriptInterface(object : Any() { @JavascriptInterface fun onReCaptchaToken(token: String) { @@ -110,7 +113,7 @@ class ReCaptchaOverlay(val context: Context, val apiKey: String, val hostname: S } companion object { - fun isSupported(context: Context): Boolean = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context) + fun isSupported(context: Context): Boolean = android.os.Build.VERSION.SDK_INT < 23 || Settings.canDrawOverlays(context) suspend fun awaitToken(context: Context, apiKey: String, hostname: String? = null) = suspendCoroutine { continuation -> ReCaptchaOverlay(context, apiKey, hostname ?: "localhost:5000", continuation).show() diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt index c13a4d5ab..a52059a91 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt @@ -1,14 +1,11 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.profile import android.annotation.TargetApi -import android.content.Context -import android.os.Build -import kotlin.random.Random object Build { @JvmField @@ -94,4 +91,11 @@ object Build { @JvmField var SECURITY_PATCH: String? = null } + + fun generateWebViewUserAgentString(original: String): String { + if (!original.startsWith("Mozilla/5.0 (")) return original + val closeParen: Int = original.indexOf(')') + + return "Mozilla/5.0 (Linux; Android ${VERSION.RELEASE}; $MODEL Build/$ID; wv)${original.substring(closeParen + 1)}" + } } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java index d139f2441..7c913e620 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java @@ -25,7 +25,6 @@ import android.graphics.Color; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -37,11 +36,11 @@ import android.webkit.CookieManager; import android.webkit.JavascriptInterface; import android.webkit.WebSettings; import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.StringRes; +import androidx.webkit.WebViewClientCompat; import com.google.android.gms.R; @@ -55,6 +54,8 @@ import org.microg.gms.checkin.LastCheckinInfo; import org.microg.gms.common.HttpFormClient; import org.microg.gms.common.Utils; import org.microg.gms.people.PeopleManager; +import org.microg.gms.profile.Build; +import org.microg.gms.profile.ProfileManager; import java.io.IOException; import java.util.Locale; @@ -106,7 +107,7 @@ public class LoginActivity extends AssistantActivity { webView.addJavascriptInterface(new JsBridge(), "mm"); authContent = (ViewGroup) findViewById(R.id.auth_content); ((ViewGroup) findViewById(R.id.auth_root)).addView(webView); - webView.setWebViewClient(new WebViewClient() { + webView.setWebViewClient(new WebViewClientCompat() { @Override public void onPageFinished(WebView view, String url) { Log.d(TAG, "pageFinished: " + view.getUrl()); @@ -135,14 +136,14 @@ public class LoginActivity extends AssistantActivity { AccountManager accountManager = AccountManager.get(this); Account account = new Account(getIntent().getStringExtra(EXTRA_EMAIL), accountType); accountManager.addAccountExplicitly(account, getIntent().getStringExtra(EXTRA_TOKEN), null); - if (isAuthVisible(this) && SDK_INT >= Build.VERSION_CODES.O) { + if (isAuthVisible(this) && SDK_INT >= 26) { accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE); } retrieveGmsToken(account); } else { retrieveRtToken(getIntent().getStringExtra(EXTRA_TOKEN)); } - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + } else if (android.os.Build.VERSION.SDK_INT < 21) { init(); } else { setMessage(R.string.auth_before_connect); @@ -200,13 +201,14 @@ public class LoginActivity extends AssistantActivity { webView.setLayoutParams(new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); webView.setBackgroundColor(Color.TRANSPARENT); - prepareWebViewSettings(webView.getSettings()); + prepareWebViewSettings(context, webView.getSettings()); return webView; } @SuppressLint("SetJavaScriptEnabled") - private static void prepareWebViewSettings(WebSettings settings) { - settings.setUserAgentString(settings.getUserAgentString() + MAGIC_USER_AGENT); + private static void prepareWebViewSettings(Context context, WebSettings settings) { + ProfileManager.ensureInitialized(context); + settings.setUserAgentString(Build.INSTANCE.generateWebViewUserAgentString(settings.getUserAgentString()) + MAGIC_USER_AGENT); settings.setJavaScriptEnabled(true); settings.setSupportMultipleWindows(false); settings.setSaveFormData(false); diff --git a/play-services-safetynet-core-ui/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt b/play-services-safetynet-core-ui/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt index 9808fcc54..7c512f64f 100644 --- a/play-services-safetynet-core-ui/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt +++ b/play-services-safetynet-core-ui/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt @@ -22,6 +22,8 @@ import androidx.lifecycle.lifecycleScope import androidx.webkit.WebViewClientCompat import com.google.android.gms.safetynet.SafetyNetStatusCodes.* import org.microg.gms.droidguard.core.DroidGuardResultCreator +import org.microg.gms.profile.Build +import org.microg.gms.profile.ProfileManager import org.microg.gms.safetynet.core.ui.R import java.io.ByteArrayInputStream import java.net.URLEncoder @@ -92,6 +94,8 @@ class ReCaptchaActivity : AppCompatActivity() { displayZoomControls = false setSupportZoom(false) cacheMode = WebSettings.LOAD_NO_CACHE + ProfileManager.ensureInitialized(this@ReCaptchaActivity) + userAgentString = Build.generateWebViewUserAgentString(userAgentString) } addJavascriptInterface(object { @JavascriptInterface -- GitLab From 75aaeb1fc65c2e349007f78898c04e045e1d830c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hetk=C3=A4mper?= Date: Tue, 1 Feb 2022 00:06:04 +0100 Subject: [PATCH 02/26] ENF: Fix average RSSI calculation Correctly discard new measurements when the reported timestamp is older than the timestamp in the database. Fixes https://github.com/microg/GmsCore/issues/1655. --- .../microg/gms/nearby/exposurenotification/ExposureDatabase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt index a702a74c6..c7ad9ab3e 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt @@ -181,7 +181,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } fun noteAdvertisement(rpi: ByteArray, aem: ByteArray, rssi: Int, timestamp: Long = Date().time) = writableDatabase.run { - val update = compileStatement("UPDATE $TABLE_ADVERTISEMENTS SET rssi = IFNULL(((rssi * duration) + (? * MIN(0, ? - timestamp - duration))) / MAX(duration, ? - timestamp), -100), duration = MAX(duration, ? - timestamp) WHERE rpi = ? AND timestamp > ? AND timestamp < ?").run { + val update = compileStatement("UPDATE $TABLE_ADVERTISEMENTS SET rssi = IFNULL(((rssi * duration) + (? * MAX(0, ? - timestamp - duration))) / MAX(duration, ? - timestamp), -100), duration = MAX(duration, ? - timestamp) WHERE rpi = ? AND timestamp > ? AND timestamp < ?").run { bindLong(1, rssi.toLong()) bindLong(2, timestamp) bindLong(3, timestamp) -- GitLab From adf534d20a56160b2d62644f44e77426ba42fef1 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 1 Feb 2022 14:47:52 +0100 Subject: [PATCH 03/26] ENF: Replace invalid RSSI values in database --- .../gms/nearby/exposurenotification/ExposureDatabase.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt index c7ad9ab3e..6c3407513 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt @@ -130,6 +130,12 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit Log.d(TAG, "Get rid of isEnabled log entries") db.delete(TABLE_APP_LOG, "method = ?", arrayOf("isEnabled")); } + if (oldVersion == 11) { + Log.d(TAG, "Fixing invalid rssi values from version 11 with release 0.2.23") + // Setting the RSSI to -75. This is obviously not the correct value, but is still way better estimate + // than 0, based on the measurements shown in https://github.com/microg/GmsCore/issues/1655 + db.execSQL("UPDATE $TABLE_ADVERTISEMENTS SET rssi = -75 WHERE rssi = 0 AND duration > 0") + } Log.d(TAG, "Finished database upgrade") } @@ -835,7 +841,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit companion object { private const val DB_NAME = "exposure.db" - private const val DB_VERSION = 11 + private const val DB_VERSION = 12 private const val DB_SIZE_TOO_LARGE = 256L * 1024 * 1024 private const val MAX_DELETE_TIME = 5000L private const val TABLE_ADVERTISEMENTS = "advertisements" -- GitLab From 17b8371b489f919d6ff87607c4a289b0af38d159 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 1 Feb 2022 20:23:12 +0100 Subject: [PATCH 04/26] Update UnifiedNlp version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 940da53a1..dfc12670b 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { ext.cronetVersion = '91.0.4472.120.1' - ext.nlpVersion = '2.0-alpha7' + ext.nlpVersion = '2.0-alpha8' ext.safeParcelVersion = '1.7.0' ext.wearableVersion = '0.1.1' -- GitLab From 412d1f5193189d69dc1515b3cb64a7a9717e15c3 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 20 Feb 2022 13:44:02 +0100 Subject: [PATCH 05/26] Update API and finish Tasks implementation --- .../com/google/android/gms/tasks/Tasks.kt | 5 +- .../android/gms/tasks/CancellationToken.java | 62 ++++- .../android/gms/tasks/Continuation.java | 12 +- .../gms/tasks/SuccessContinuation.java | 18 +- .../com/google/android/gms/tasks/Task.java | 12 +- .../gms/tasks/TaskCompletionSource.java | 31 ++- .../com/google/android/gms/tasks/Tasks.java | 240 ++++++++++++++++-- 7 files changed, 345 insertions(+), 35 deletions(-) diff --git a/play-services-tasks-ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt b/play-services-tasks-ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt index 3171dd6e7..134b95be6 100644 --- a/play-services-tasks-ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt +++ b/play-services-tasks-ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt @@ -1,12 +1,11 @@ /* - * SPDX-FileCopyrightText: 2016, JetBrains s.r.o. - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2016 JetBrains s.r.o. + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package com.google.android.gms.tasks -import com.google.android.gms.tasks.* import kotlinx.coroutines.* import kotlin.coroutines.* diff --git a/play-services-tasks/src/main/java/com/google/android/gms/tasks/CancellationToken.java b/play-services-tasks/src/main/java/com/google/android/gms/tasks/CancellationToken.java index 00563c5b0..ecdd77cab 100644 --- a/play-services-tasks/src/main/java/com/google/android/gms/tasks/CancellationToken.java +++ b/play-services-tasks/src/main/java/com/google/android/gms/tasks/CancellationToken.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-FileCopyrightText: 2020 microG Project Team * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 * Notice: Portions of this file are reproduced from work created and shared by Google and used * according to terms described in the Creative Commons 4.0 Attribution License. @@ -10,6 +10,66 @@ package com.google.android.gms.tasks; import org.microg.gms.common.PublicApi; +/** + * Propagates notification that operations should be canceled. + *

+ * Developers writing methods that return a Task should take a {@code CancellationToken} as a parameter if they wish to + * make the Task cancelable (see below code snippet). A {@code CancellationToken} can only be created by creating a new + * instance of {@link CancellationTokenSource}. {@code CancellationToken} is immutable and must be canceled by calling + * {@link CancellationTokenSource#cancel()} on the {@link CancellationTokenSource} that creates it. It can only be + * canceled once. If canceled, it should not be passed to future operations. + *

+ * When {@link CancellationTokenSource#cancel()} is called, all the Tasks with the {@code CancellationToken} from that + * {@link CancellationTokenSource} will be canceled. This operation only flags those Tasks as canceled, and the API + * author is responsible for stopping whatever the Task is actually doing to free up the resources. + *

+ * Cancellable {@link Task} example: + *

+ * public Task doSomething(CancellationToken token) {
+ *
+ *     // Attach a listener that will be called once cancellation is requested.
+ *     token.onCanceledRequested(new OnTokenCanceledListener() {
+ *         @Override
+ *         public void onCanceled() {
+ *             // Some other operations to cancel this Task, such as free resources...
+ *         }
+ *     });
+ *
+ *     final TaskCompletionSource tcs = new TaskCompletionSource<>(token);
+ *
+ *     // do something...
+ *
+ * }
+ *
+ * CancellationTokenSource cts = new CancellationTokenSource();
+ * Task task = doSomething(cts.getToken());
+ * cts.cancel();
+ * 
+ * Cancellable {@link Task} example in {@link android.app.Activity} context: + *
+ * public class MyActivity extends Activity {
+ *     // ...
+ *
+ *     @Override
+ *     public void onStart() {
+ *         super.onStart();
+ *
+ *         // Typically use one cancellation source per lifecycle.
+ *         cancellationSource = new TaskCancellationSource();
+ *
+ *         // That source's token can be passed to multiple calls.
+ *         doSomethingCancellable(cancellationSource.getToken())
+ *             .onSuccessTask(result -> doSomethingElse(result, cancellationSource.getToken()));
+ *     }
+ *
+ *     @Override
+ *     public void onStop() {
+ *         super.onStop();
+ *         cancellationSource.cancel();
+ *     }
+ * }
+ * 
+ */ @PublicApi public abstract class CancellationToken { /** diff --git a/play-services-tasks/src/main/java/com/google/android/gms/tasks/Continuation.java b/play-services-tasks/src/main/java/com/google/android/gms/tasks/Continuation.java index 7057ca716..db26d0e93 100644 --- a/play-services-tasks/src/main/java/com/google/android/gms/tasks/Continuation.java +++ b/play-services-tasks/src/main/java/com/google/android/gms/tasks/Continuation.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016, microG Project Team + * SPDX-FileCopyrightText: 2016 microG Project Team * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 * Notice: Portions of this file are reproduced from work created and shared by Google and used * according to terms described in the Creative Commons 4.0 Attribution License. @@ -28,8 +28,9 @@ public interface Continuation { *

* To suppress specific failures call {@link Task#getResult(Class)} and catch the exception * types of interest: - *

task.continueWith(new Continuation() {
-     *     @Override
+     * 
+     * task.continueWith(new Continuation() {
+     *     @Override
      *     public String then(Task task) {
      *         try {
      *             return task.getResult(IOException.class);
@@ -42,8 +43,9 @@ public interface Continuation {
      * }
*

* To suppress all failures guard any calls to {@link Task#getResult()} with {@link Task#isSuccessful()}: - *

task.continueWith(new Continuation() {
-     *     @Override
+     * 
+     * task.continueWith(new Continuation() {
+     *     @Override
      *     public String then(Task task) {
      *         if (task.isSuccessful()) {
      *             return task.getResult();
diff --git a/play-services-tasks/src/main/java/com/google/android/gms/tasks/SuccessContinuation.java b/play-services-tasks/src/main/java/com/google/android/gms/tasks/SuccessContinuation.java
index 3feb598ed..26b49e88f 100644
--- a/play-services-tasks/src/main/java/com/google/android/gms/tasks/SuccessContinuation.java
+++ b/play-services-tasks/src/main/java/com/google/android/gms/tasks/SuccessContinuation.java
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: 2020, microG Project Team
+ * SPDX-FileCopyrightText: 2020 microG Project Team
  * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0
  * Notice: Portions of this file are reproduced from work created and shared by Google and used
  *         according to terms described in the Creative Commons 4.0 Attribution License.
@@ -19,7 +19,21 @@ public interface SuccessContinuation {
     /**
      * Returns the result of applying this SuccessContinuation to Task.
      * 

- * The SuccessContinuation only happens then the Task is successful. If the previous Task fails, the onSuccessTask continuation will be skipped and failure listeners will be invoked. + * The SuccessContinuation only happens then the Task is successful. If the previous Task fails, the onSuccessTask + * continuation will be skipped and failure listeners will be invoked. + *

+ *

+     * private Task doSomething(String string) {
+     *     // do something
+     * }
+     * task.onSuccessTask(new SuccessContinuation() {
+     *     @NonNull
+     *     @Override
+     *     public Task then(String string) {
+     *         return doSomething(string);
+     *     }
+     * });
+     * 
* * @param result the result of completed Task * @throws Exception if the result couldn't be produced diff --git a/play-services-tasks/src/main/java/com/google/android/gms/tasks/Task.java b/play-services-tasks/src/main/java/com/google/android/gms/tasks/Task.java index f72237796..7cf394779 100644 --- a/play-services-tasks/src/main/java/com/google/android/gms/tasks/Task.java +++ b/play-services-tasks/src/main/java/com/google/android/gms/tasks/Task.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016, microG Project Team + * SPDX-FileCopyrightText: 2016 microG Project Team * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 * Notice: Portions of this file are reproduced from work created and shared by Google and used * according to terms described in the Creative Commons 4.0 Attribution License. @@ -177,6 +177,8 @@ public abstract class Task { * Continuation to this Task. *

* The Continuation will be called on the main application thread. + *

+ * If the previous Task is canceled, the returned Task will also be canceled and the Continuation would not execute. * * @see Continuation#then(Task) */ @@ -186,6 +188,8 @@ public abstract class Task { /** * Returns a new Task that will be completed with the result of applying the specified Continuation to this Task. + *

+ * If the previous Task is canceled, the returned Task will also be canceled and the Continuation would not execute. * * @param executor the executor to use to call the Continuation * @see Continuation#then(Task) @@ -199,6 +203,9 @@ public abstract class Task { * Continuation to this Task. *

* The Continuation will be called on the main application thread. + *

+ * If the previous Task is canceled, the Continuation would still execute and task.isCanceled() is true can be + * observed in the Continuation. * * @see Continuation#then(Task) */ @@ -208,6 +215,9 @@ public abstract class Task { /** * Returns a new Task that will be completed with the result of applying the specified Continuation to this Task. + *

+ * If the previous Task is canceled, the Continuation would still execute and task.isCanceled() is true can be + * observed in the Continuation. * * @param executor the executor to use to call the Continuation * @see Continuation#then(Task) diff --git a/play-services-tasks/src/main/java/com/google/android/gms/tasks/TaskCompletionSource.java b/play-services-tasks/src/main/java/com/google/android/gms/tasks/TaskCompletionSource.java index 00c4a9724..4dbe50194 100644 --- a/play-services-tasks/src/main/java/com/google/android/gms/tasks/TaskCompletionSource.java +++ b/play-services-tasks/src/main/java/com/google/android/gms/tasks/TaskCompletionSource.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016, microG Project Team + * SPDX-FileCopyrightText: 2016 microG Project Team * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 * Notice: Portions of this file are reproduced from work created and shared by Google and used * according to terms described in the Creative Commons 4.0 Attribution License. @@ -12,7 +12,34 @@ import org.microg.gms.common.PublicApi; import org.microg.gms.tasks.TaskImpl; /** - * Provides the ability to create an incomplete {@link Task} and later complete it by either + * Provides the ability to create an incomplete {@link Task}-based APIs. + *

+ * Use a {@code TaskCompletionSource} to set a result or exception on a Task returned from an asynchronous API: + *

+ * public class MarcoPolo {
+ *     public static Task marco(int delay) {
+ *         TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>();
+ *
+ *         new Handler().postDelayed(() -> taskCompletionSource.setResult("polo"), delay);
+ *
+ *         return taskCompletionSource.getTask();
+ *     }
+ * }
+ * 
+ * And then your APIs can be used as any other {@link Task}-consuming APIs: + *
+ * public class MyActivity extends Activity {
+ *     @Override
+ *     public void onStart() {
+ *         super.onStart();
+ *
+ *         marco(1000).addOnCompleteListener(
+ *             task -> Log.d(TAG, "got message after one second: " + task.getResult()));
+ *     }
+ * }
+ * 
+ * + * and later complete it by either * calling {@link #setResult(TResult)} or {@link #setException(Exception)}. */ @PublicApi diff --git a/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java b/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java index 434525622..eabc05539 100644 --- a/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java +++ b/play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016, microG Project Team + * SPDX-FileCopyrightText: 2016 microG Project Team * SPDX-License-Identifier: Apache-2.0 AND CC-BY-4.0 * Notice: Portions of this file are reproduced from work created and shared by Google and used * according to terms described in the Creative Commons 4.0 Attribution License. @@ -8,15 +8,28 @@ package com.google.android.gms.tasks; +import android.os.Handler; +import android.os.Looper; + import org.microg.gms.common.PublicApi; +import org.microg.gms.tasks.CancellationTokenImpl; +import org.microg.gms.tasks.TaskImpl; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; /** * {@link Task} utility methods. @@ -32,9 +45,17 @@ public final class Tasks { * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete * @throws TimeoutException if the specified timeout is reached before the Task completes */ - public static TResult await(Task task, long timeout, TimeUnit unit) { - // TODO - return null; + public static TResult await(Task task, long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { + if (task == null) throw new IllegalArgumentException("Task must not be null"); + if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive"); + if (unit == null) throw new IllegalArgumentException("TimeUnit must not be null"); + if (task.isComplete()) return handleCompletedTask(task); + CountDownLatch latch = new CountDownLatch(1); + task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown()); + if (latch.await(timeout, unit)) { + return handleCompletedTask(task); + } + throw new TimeoutException("Timed out waiting for Task"); } /** @@ -44,65 +65,242 @@ public final class Tasks { * @throws ExecutionException if the Task fails * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete */ - public static TResult await(Task task) { - // TODO - return null; + public static TResult await(Task task) throws ExecutionException, InterruptedException { + if (Looper.getMainLooper().getThread() == Thread.currentThread()) + throw new IllegalStateException("Must not be invoked on main thread"); + if (task == null) throw new IllegalArgumentException("Task must not be null"); + if (task.isComplete()) return handleCompletedTask(task); + CountDownLatch latch = new CountDownLatch(1); + task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown()); + latch.await(); + return handleCompletedTask(task); + } + + private static TResult handleCompletedTask(Task task) throws ExecutionException { + if (task.isSuccessful()) { + return task.getResult(); + } + if (task.isCanceled()) { + throw new CancellationException("Task is already canceled"); + } + throw new ExecutionException(task.getException()); } /** - * Returns a Task that will be completed with the result of the specified Callable. + * Returns a {@link Task} that will be completed with the result of the specified {@code Callable}. *

- * The Callable will be called on the main application thread. + * If a non-{@link Exception} throwable is thrown in the callable, the {@link Task} will be failed with a + * {@link RuntimeException} whose cause is the original throwable. + *

+ * The {@code Callable} will be called on the main application thread. + * + * @deprecated Use {@link TaskCompletionSource} instead, which allows the caller to manage their own Executor. */ + @Deprecated public static Task call(Callable callable) { - // TODO - return null; + return call(TaskExecutors.MAIN_THREAD, callable); } /** - * Returns a Task that will be completed with the result of the specified Callable. + * Returns a {@link Task} that will be completed with the result of the specified {@code Callable}. + *

+ * If a non-{@link Exception} throwable is thrown in the callable, the {@link Task} will be failed with a + * {@link RuntimeException} whose cause is the original throwable. * - * @param executor the Executor to use to call the Callable + * @param executor the Executor to use to call the {@code Callable} + * @deprecated Use {@link TaskCompletionSource} instead, which allows the caller to manage their own Executor. */ + @Deprecated public static Task call(Executor executor, Callable callable) { - // TODO - return null; + if (executor == null) throw new IllegalArgumentException("Executor must not be null"); + if (callable == null) throw new IllegalArgumentException("Callable must not be null"); + TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); + executor.execute(() -> { + try { + taskCompletionSource.setResult(callable.call()); + } catch (Exception e) { + taskCompletionSource.trySetException(e); + } catch (Throwable t) { + taskCompletionSource.trySetException(new RuntimeException(t)); + } + }); + return taskCompletionSource.getTask(); + } + + /** + * Returns a canceled Task. + */ + public static Task forCancelled() { + TaskImpl task = new TaskImpl<>(); + task.cancel(); + return task; } /** * Returns a completed Task with the specified exception. */ public static Task forException(Exception e) { - // TODO - return null; + TaskImpl task = new TaskImpl<>(); + task.setException(e); + return task; } /** * Returns a completed Task with the specified result. */ public static Task forResult(TResult result) { - // TODO - return null; + TaskImpl task = new TaskImpl<>(); + task.setResult(result); + return task; } /** * Returns a Task that completes successfully when all of the specified Tasks complete * successfully. Does not accept nulls. + *

+ * The returned Task would fail if any of the provided Tasks fail. The returned Task would be set to canceled if + * any of the provided Tasks is canceled and no failure is detected. * * @throws NullPointerException if any of the provided Tasks are null */ public static Task whenAll(Collection> tasks) { - // TODO - return null; + if (tasks == null || tasks.isEmpty()) { + return forResult(null); + } + for (Task task : tasks) { + if (task == null) throw new NullPointerException("null tasks are not accepted"); + } + TaskImpl allTask = new TaskImpl<>(); + AtomicInteger finishedTasks = new AtomicInteger(0); + AtomicInteger failedTasks = new AtomicInteger(0); + AtomicReference exceptionReference = new AtomicReference<>(null); + AtomicBoolean isCancelled = new AtomicBoolean(false); + for (Task task : tasks) { + task.addOnCompleteListener(Runnable::run, completedTask -> { + if (!completedTask.isSuccessful()) { + if (completedTask.isCanceled()) { + isCancelled.set(true); + } else { + exceptionReference.set(completedTask.getException()); + failedTasks.incrementAndGet(); + } + } + if (finishedTasks.incrementAndGet() != tasks.size()) return; + Exception exception = exceptionReference.get(); + if (exception != null) { + allTask.setException(new ExecutionException(failedTasks.get() + " out of " + tasks.size() + " underlying tasks failed", exception)); + } else if (isCancelled.get()) { + allTask.cancel(); + } else { + allTask.setResult(null); + } + }); + } + return allTask; } /** * Returns a Task that completes successfully when all of the specified Tasks complete * successfully. Does not accept nulls. + *

+ * The returned Task would fail if any of the provided Tasks fail. The returned Task would be set to canceled if + * any of the provided Tasks is canceled and no failure is detected. * * @throws NullPointerException if any of the provided Tasks are null */ public static Task whenAll(Task... tasks) { + if (tasks == null || tasks.length == 0) { + return forResult(null); + } return whenAll(Arrays.asList(tasks)); } + + /** + * Returns a Task with a list of Tasks that completes successfully when all of the specified Tasks complete. This + * Task would always succeed even if any of the provided Tasks fail or canceled. Does not accept nulls. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task>> whenAllComplete(Task... tasks) { + if (tasks == null || tasks.length == 0) { + return forResult(Collections.emptyList()); + } + return whenAllComplete(Arrays.asList(tasks)); + } + + /** + * Returns a Task with a list of Tasks that completes successfully when all of the specified Tasks complete. This + * Task would always succeed even if any of the provided Tasks fail or canceled. Does not accept nulls. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task>> whenAllComplete(Collection> tasks) { + if (tasks == null || tasks.isEmpty()) { + return forResult(Collections.emptyList()); + } + return whenAll(tasks).continueWithTask(TaskExecutors.MAIN_THREAD, allTask -> forResult(new ArrayList<>(tasks))); + } + + /** + * Returns a Task with a list of Task results that completes successfully when all of the specified Tasks complete + * successfully. This Task would fail if any of the provided Tasks fail. Does not accept nulls. + *

+ * This Task would be set to canceled if any of the provided Tasks is canceled and no failure is detected. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task> whenAllSuccess(Task... tasks) { + if (tasks == null || tasks.length == 0) { + return forResult(Collections.emptyList()); + } + return whenAllSuccess(Arrays.asList(tasks)); + } + + /** + * Returns a Task with a list of Task results that completes successfully when all of the specified Tasks complete + * successfully. This Task would fail if any of the provided Tasks fail. Does not accept nulls. + *

+ * This Task would be set to canceled if any of the provided Tasks is canceled and no failure is detected. + * + * @throws NullPointerException if any of the provided Tasks are null + */ + public static Task> whenAllSuccess(Collection> tasks) { + if (tasks == null || tasks.isEmpty()) { + return forResult(Collections.emptyList()); + } + return whenAll(tasks).continueWithTask(TaskExecutors.MAIN_THREAD, allTask -> { + List results = new ArrayList<>(tasks.size()); + for (Task task : tasks) { + results.add(task.getResult()); + } + return forResult(results); + }); + } + + /** + * Returns a new Task which will return a TimeoutException if a result is not returned within the specified time + * period. + * + * @return A new Task. + */ + public static Task withTimeout(Task task, long timeout, TimeUnit unit) { + if (task == null) throw new IllegalArgumentException("Task must not be null"); + if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive"); + if (unit == null) throw new IllegalArgumentException("TimeUnit must not be null"); + CancellationTokenImpl cancellationToken = new CancellationTokenImpl(); + TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(cancellationToken); + Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(() -> taskCompletionSource.trySetException(new TimeoutException()), unit.toMillis(timeout)); + task.addOnCompleteListener(completedTask -> { + handler.removeCallbacksAndMessages(null); + if (completedTask.isSuccessful()) { + taskCompletionSource.trySetResult(completedTask.getResult()); + } else if (completedTask.isCanceled()) { + cancellationToken.cancel(); + } else { + taskCompletionSource.trySetException(completedTask.getException()); + } + }); + return taskCompletionSource.getTask(); + } } -- GitLab From f40df95fd444e034161d368889656a874f35d947 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 21 Feb 2022 19:50:44 +0100 Subject: [PATCH 06/26] Clone dependency tree from original play-services libraries --- build.gradle | 5 ++-- play-services-base/build.gradle | 9 +++++--- play-services-basement/build.gradle | 5 +++- play-services-cast/build.gradle | 24 ++++++++------------ play-services-gcm/build.gradle | 24 ++++++++------------ play-services-iid/build.gradle | 24 ++++++++------------ play-services-location/build.gradle | 22 ++++++------------ play-services-nearby/build.gradle | 10 ++++++-- play-services-vision-api/build.gradle | 2 -- play-services-vision-common-api/build.gradle | 2 -- play-services-vision-common/build.gradle | 10 +++++--- play-services-vision/build.gradle | 5 +++- play-services-wearable/build.gradle | 22 +++++++----------- 13 files changed, 75 insertions(+), 89 deletions(-) diff --git a/build.gradle b/build.gradle index dfc12670b..2b6402dfa 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2013, microG Project Team + * SPDX-FileCopyrightText: 2013 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ @@ -12,11 +12,12 @@ buildscript { ext.kotlinVersion = '1.6.10' ext.coroutineVersion = '1.5.2' - ext.annotationVersion = '1.2.0' + ext.annotationVersion = '1.3.0' ext.appcompatVersion = '1.4.1' ext.coreVersion = '1.7.0' ext.fragmentVersion = '1.4.0' ext.lifecycleVersion = '2.4.0' + ext.loaderVersion = '1.1.0' ext.mediarouterVersion = '1.2.5' ext.multidexVersion = '2.0.1' ext.navigationVersion = '2.3.5' diff --git a/play-services-base/build.gradle b/play-services-base/build.gradle index b87841a88..5f9809589 100644 --- a/play-services-base/build.gradle +++ b/play-services-base/build.gradle @@ -39,9 +39,12 @@ apply from: '../gradle/publish-android.gradle' description = 'microG implementation of play-services-base' dependencies { - api project(':play-services-basement') - api project(':play-services-tasks') api project(':play-services-base-api') - implementation "androidx.fragment:fragment:$fragmentVersion" + // Dependencies from play-services-base:18.0.1 + api "androidx.collection:collection:1.0.0" + api "androidx.core:core:1.2.0" + api "androidx.fragment:fragment:1.0.0" + api project(':play-services-basement') + api project(':play-services-tasks') } diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index 30acbe04e..9f4662e0a 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -21,7 +21,10 @@ apply plugin: 'signing' dependencies { api "org.microg:safe-parcel:$safeParcelVersion" - implementation "androidx.annotation:annotation:$annotationVersion" + // Dependencies from play-services-basement:18.0.0 + api "androidx.collection:collection:1.0.0" + api "androidx.core:core:1.2.0" + api "androidx.fragment:fragment:1.0.0" } android { diff --git a/play-services-cast/build.gradle b/play-services-cast/build.gradle index 85ea138a4..df21e2e35 100644 --- a/play-services-cast/build.gradle +++ b/play-services-cast/build.gradle @@ -1,17 +1,6 @@ /* - * Copyright 2013-2015 microG Project Team - * - * 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. + * SPDX-FileCopyrightText: 2015 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.library' @@ -42,6 +31,13 @@ android { } dependencies { - api project(':play-services-base') api project(':play-services-cast-api') + + // Dependencies from play-services-cast:21.0.1 + api "androidx.core:core:1.0.0" + api "androidx.mediarouter:mediarouter:1.2.2" + api project(':play-services-base') + api project(':play-services-basement') + //api project(':play-services-flags') + api project(':play-services-tasks') } diff --git a/play-services-gcm/build.gradle b/play-services-gcm/build.gradle index 26dc76633..c98e80b51 100644 --- a/play-services-gcm/build.gradle +++ b/play-services-gcm/build.gradle @@ -1,17 +1,6 @@ /* - * Copyright 2013-2015 microG Project Team - * - * 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. + * SPDX-FileCopyrightText: 2016 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.library' @@ -42,7 +31,12 @@ android { } dependencies { + // Dependencies from play-services-gcm:17.0.0 + api "androidx.collection:collection:1.0.0" + api "androidx.core:core:1.0.0" + api 'androidx.legacy:legacy-support-core-utils:1.0.0' + api project(':play-services-base') + api project(':play-services-basement') api project(':play-services-iid') - - implementation 'androidx.legacy:legacy-support-core-utils:1.0.0' // TODO + //api project(':play-services-stats') } diff --git a/play-services-iid/build.gradle b/play-services-iid/build.gradle index 5edc41516..c38cc4f47 100644 --- a/play-services-iid/build.gradle +++ b/play-services-iid/build.gradle @@ -1,17 +1,6 @@ /* - * Copyright 2013-2015 microG Project Team - * - * 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. + * SPDX-FileCopyrightText: 2016 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.library' @@ -42,8 +31,13 @@ android { } dependencies { - api project(':play-services-base') api project(':play-services-iid-api') - implementation 'androidx.legacy:legacy-support-core-utils:1.0.0' // TODO + // Dependencies from play-services-iid:17.0.0 + api "androidx.collection:collection:1.0.0" + api "androidx.core:core:1.0.0" + api project(':play-services-base') + api project(':play-services-basement') + //api project(':play-services-stats') + api project(':play-services-tasks') } diff --git a/play-services-location/build.gradle b/play-services-location/build.gradle index 57f5027b4..437a774b7 100644 --- a/play-services-location/build.gradle +++ b/play-services-location/build.gradle @@ -1,17 +1,6 @@ /* - * Copyright 2013-2015 microG Project Team - * - * 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. + * SPDX-FileCopyrightText: 2015 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.library' @@ -42,8 +31,11 @@ android { } dependencies { - api project(':play-services-base') api project(':play-services-location-api') - implementation 'androidx.annotation:annotation:1.1.0' + // Dependencies from play-services-location:19.0.1 + api project(':play-services-base') + api project(':play-services-basement') + //api project(':play-services-places-placereport') + api project(':play-services-tasks') } diff --git a/play-services-nearby/build.gradle b/play-services-nearby/build.gradle index 6d2d5a1fa..ec6102c7b 100644 --- a/play-services-nearby/build.gradle +++ b/play-services-nearby/build.gradle @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-FileCopyrightText: 2020 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ @@ -28,6 +28,12 @@ apply from: '../gradle/publish-android.gradle' description = 'microG implementation of play-services-nearby' dependencies { - api project(':play-services-base') api project(':play-services-nearby-api') + + // Dependencies from play-services-nearby:18.0.2 + api "androidx.collection:collection:1.0.0" + api "androidx.core:core:1.0.0" + api project(':play-services-base') + api project(':play-services-basement') + api project(':play-services-tasks') } diff --git a/play-services-vision-api/build.gradle b/play-services-vision-api/build.gradle index 0a5e65628..c25f5f50f 100644 --- a/play-services-vision-api/build.gradle +++ b/play-services-vision-api/build.gradle @@ -31,6 +31,4 @@ dependencies { api project(':play-services-basement') api project(':play-services-base-api') api project(':play-services-vision-common-api') - - implementation "androidx.annotation:annotation:$annotationVersion" } diff --git a/play-services-vision-common-api/build.gradle b/play-services-vision-common-api/build.gradle index feeb0e4fb..63f5c10e6 100644 --- a/play-services-vision-common-api/build.gradle +++ b/play-services-vision-common-api/build.gradle @@ -30,6 +30,4 @@ description = 'microG API for play-services-vision-common' dependencies { api project(':play-services-basement') api project(':play-services-base-api') - - implementation "androidx.annotation:annotation:$annotationVersion" } diff --git a/play-services-vision-common/build.gradle b/play-services-vision-common/build.gradle index 6f19baabe..a2ddace25 100644 --- a/play-services-vision-common/build.gradle +++ b/play-services-vision-common/build.gradle @@ -28,8 +28,12 @@ apply from: '../gradle/publish-android.gradle' description = 'microG implementation of play-services-vision-common' dependencies { - api project(':play-services-base') - api project(':play-services-vision-api') + api project(':play-services-vision-common-api') - implementation "androidx.annotation:annotation:$annotationVersion" + // Dependencies from play-services-vision-common:19.1.3 + api project(':play-services-base') + api project(':play-services-basement') + //api project(':play-services-clearcut') + //api project(':play-services-flags') + //api project(':play-services-phenotype') } diff --git a/play-services-vision/build.gradle b/play-services-vision/build.gradle index 04f00b503..142d368c1 100644 --- a/play-services-vision/build.gradle +++ b/play-services-vision/build.gradle @@ -28,7 +28,10 @@ apply from: '../gradle/publish-android.gradle' description = 'microG implementation of play-services-vision' dependencies { - api project(':play-services-base') api project(':play-services-vision-api') + + // Dependencies from play-services-vision:20.1.3 + api project(':play-services-base') + api project(':play-services-basement') api project(':play-services-vision-common') } diff --git a/play-services-wearable/build.gradle b/play-services-wearable/build.gradle index 2fd071bfa..da924ef65 100644 --- a/play-services-wearable/build.gradle +++ b/play-services-wearable/build.gradle @@ -1,17 +1,6 @@ /* - * Copyright 2013-2015 microG Project Team - * - * 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. + * SPDX-FileCopyrightText: 2015 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.library' @@ -37,8 +26,13 @@ android { } dependencies { - api project(':play-services-base') api project(':play-services-wearable-api') api project(':play-services-wearable-proto') implementation "com.squareup.wire:wire-runtime:$wireVersion" + + // Dependencies from play-services-wearable:17.1.0 + implementation "androidx.core:core:1.0.0" + api project(':play-services-base') + api project(':play-services-basement') + api project(':play-services-tasks') } -- GitLab From d8aedfc743af561e8488e2ae186d2910e0fecabb Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 24 Feb 2022 14:28:32 +0100 Subject: [PATCH 07/26] Add play-services-oss-licenses library --- .../org/microg/gms/common/GmsService.java | 1 + play-services-core/build.gradle | 16 +- play-services-oss-licenses-api/build.gradle | 33 +++ .../src/main/AndroidManifest.xml | 6 + .../gms/oss/licenses/IOSSLicenseService.aidl | 10 + .../android/gms/oss/licenses/License.aidl | 5 + .../android/gms/oss/licenses/License.java | 81 +++++++ .../microg/gms/oss/licenses/LicenseUtil.java | 104 +++++++++ play-services-oss-licenses-core/build.gradle | 45 ++++ .../src/main/AndroidManifest.xml | 18 ++ .../oss/licenses/core/OssLicensesService.kt | 50 +++++ play-services-oss-licenses/build.gradle | 39 ++++ .../src/main/AndroidManifest.xml | 15 ++ .../gms/oss/licenses/OssLicensesActivity.java | 128 +++++++++++ .../oss/licenses/OssLicensesMenuActivity.java | 211 ++++++++++++++++++ .../oss/licenses/OssLicensesServiceImpl.java | 78 +++++++ .../licenses/OssLicenseServiceApiClient.java | 48 ++++ .../libraries_social_licenses_license.xml | 22 ++ ...aries_social_licenses_license_activity.xml | 25 +++ ..._social_licenses_license_menu_activity.xml | 20 ++ .../license_menu_activity_no_licenses.xml | 18 ++ .../src/main/res/values/strings.xml | 13 ++ settings.gradle | 3 + 23 files changed, 976 insertions(+), 13 deletions(-) create mode 100644 play-services-oss-licenses-api/build.gradle create mode 100644 play-services-oss-licenses-api/src/main/AndroidManifest.xml create mode 100644 play-services-oss-licenses-api/src/main/aidl/com/google/android/gms/oss/licenses/IOSSLicenseService.aidl create mode 100644 play-services-oss-licenses-api/src/main/aidl/com/google/android/gms/oss/licenses/License.aidl create mode 100644 play-services-oss-licenses-api/src/main/java/com/google/android/gms/oss/licenses/License.java create mode 100644 play-services-oss-licenses-api/src/main/java/org/microg/gms/oss/licenses/LicenseUtil.java create mode 100644 play-services-oss-licenses-core/build.gradle create mode 100644 play-services-oss-licenses-core/src/main/AndroidManifest.xml create mode 100644 play-services-oss-licenses-core/src/main/kotlin/org/microg/gms/oss/licenses/core/OssLicensesService.kt create mode 100644 play-services-oss-licenses/build.gradle create mode 100644 play-services-oss-licenses/src/main/AndroidManifest.xml create mode 100644 play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesActivity.java create mode 100644 play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesMenuActivity.java create mode 100644 play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesServiceImpl.java create mode 100644 play-services-oss-licenses/src/main/java/org/microg/gms/oss/licenses/OssLicenseServiceApiClient.java create mode 100644 play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license.xml create mode 100644 play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license_activity.xml create mode 100644 play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license_menu_activity.xml create mode 100644 play-services-oss-licenses/src/main/res/layout/license_menu_activity_no_licenses.xml create mode 100644 play-services-oss-licenses/src/main/res/values/strings.xml diff --git a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java index 108eb625a..91e54c1ba 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java @@ -125,6 +125,7 @@ public enum GmsService { FIDO2_ZEROPARTY(180, "com.google.android.gms.fido.fido2.zeroparty.START"), G1_RESTORE(181, "com.google.android.gms.backup.G1_RESTORE"), G1_BACKUP(182, "com.google.android.gms.backup.G1_BACKUP"), + OSS_LICENSES(185, "com.google.android.gms.oss.licenses.service.START"), PAYSE(188, "com.google.android.gms.payse.service.BIND"), RCS(189, "com.google.android.gms.rcs.START"), CARRIER_AUTH(191, "com.google.android.gms.carrierauth.service.START"), diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 285970ad8..75458ee45 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -1,17 +1,6 @@ /* - * Copyright 2013-2019 microG Project Team - * - * 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. + * SPDX-FileCopyrightText: 2013 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.application' @@ -50,6 +39,7 @@ dependencies { withNearbyImplementation project(':play-services-nearby-core') withNearbyImplementation project(':play-services-nearby-core-ui') implementation project(':play-services-safetynet-core') + implementation project(':play-services-oss-licenses-core') implementation project(':play-services-safetynet-core-ui') implementation project(':play-services-tapandpay-core') implementation project(':play-services-vision-core') diff --git a/play-services-oss-licenses-api/build.gradle b/play-services-oss-licenses-api/build.gradle new file mode 100644 index 000000000..b1435c702 --- /dev/null +++ b/play-services-oss-licenses-api/build.gradle @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG API for play-services-oss-licenses' + +dependencies { + api project(':play-services-basement') + api project(':play-services-base-api') +} diff --git a/play-services-oss-licenses-api/src/main/AndroidManifest.xml b/play-services-oss-licenses-api/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ba30fb23e --- /dev/null +++ b/play-services-oss-licenses-api/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/play-services-oss-licenses-api/src/main/aidl/com/google/android/gms/oss/licenses/IOSSLicenseService.aidl b/play-services-oss-licenses-api/src/main/aidl/com/google/android/gms/oss/licenses/IOSSLicenseService.aidl new file mode 100644 index 000000000..2ecaae99b --- /dev/null +++ b/play-services-oss-licenses-api/src/main/aidl/com/google/android/gms/oss/licenses/IOSSLicenseService.aidl @@ -0,0 +1,10 @@ +package com.google.android.gms.oss.licenses; + +import com.google.android.gms.oss.licenses.License; + +interface IOSSLicenseService { + String getListLayoutPackage(String packageName) = 1; + String getLicenseLayoutPackage(String packageName) = 2; + String getLicenseDetail(String license) = 3; + List getLicenseList(in List list) = 4; +} diff --git a/play-services-oss-licenses-api/src/main/aidl/com/google/android/gms/oss/licenses/License.aidl b/play-services-oss-licenses-api/src/main/aidl/com/google/android/gms/oss/licenses/License.aidl new file mode 100644 index 000000000..d85a531e4 --- /dev/null +++ b/play-services-oss-licenses-api/src/main/aidl/com/google/android/gms/oss/licenses/License.aidl @@ -0,0 +1,5 @@ +package com.google.android.gms.oss.licenses; + +import com.google.android.gms.oss.licenses.License; + +parcelable License; diff --git a/play-services-oss-licenses-api/src/main/java/com/google/android/gms/oss/licenses/License.java b/play-services-oss-licenses-api/src/main/java/com/google/android/gms/oss/licenses/License.java new file mode 100644 index 000000000..5255bd9c0 --- /dev/null +++ b/play-services-oss-licenses-api/src/main/java/com/google/android/gms/oss/licenses/License.java @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.oss.licenses; + +import android.os.Parcel; +import android.os.Parcelable; + +public class License implements Parcelable, Comparable { + public static final Creator CREATOR = new Creator() { + @Override + public License createFromParcel(Parcel source) { + return new License(source); + } + + @Override + public License[] newArray(int size) { + return new License[size]; + } + }; + + private final String name; + private final long offset; + private final int length; + private final String path; + + public License(String name, long offset, int length, String path) { + this.name = name; + this.offset = offset; + this.length = length; + this.path = path; + } + + public License(Parcel parcel) { + this.name = parcel.readString(); + this.offset = parcel.readLong(); + this.length = parcel.readInt(); + this.path = parcel.readString(); + } + + public String getName() { + return name; + } + + public long getOffset() { + return offset; + } + + public int getLength() { + return length; + } + + public String getPath() { + return path; + } + + @Override + public int compareTo(License other) { + return name.compareToIgnoreCase(other.name); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(name); + dest.writeLong(offset); + dest.writeInt(length); + dest.writeString(path); + } + + @Override + public String toString() { + return name; + } +} diff --git a/play-services-oss-licenses-api/src/main/java/org/microg/gms/oss/licenses/LicenseUtil.java b/play-services-oss-licenses-api/src/main/java/org/microg/gms/oss/licenses/LicenseUtil.java new file mode 100644 index 000000000..d2208cec9 --- /dev/null +++ b/play-services-oss-licenses-api/src/main/java/org/microg/gms/oss/licenses/LicenseUtil.java @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.oss.licenses; + +import android.content.Context; +import android.content.res.Resources; + +import com.google.android.gms.oss.licenses.License; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class LicenseUtil { + public static boolean hasLicenses(Context context) { + Resources resources = context.getApplicationContext().getResources(); + try (InputStream is = resources.openRawResource(resources.getIdentifier("third_party_license_metadata", "raw", context.getPackageName()))) { + if (is == null || is.available() <= 0) return false; + } catch (IOException e) { + return false; + } + try (InputStream is = resources.openRawResource(resources.getIdentifier("third_party_licenses", "raw", context.getPackageName()))) { + if (is == null || is.available() <= 0) return false; + } catch (IOException e) { + return false; + } + return true; + } + + public static List getLicensesFromMetadata(Context context) { + Resources resources = context.getApplicationContext().getResources(); + InputStream is = resources.openRawResource(resources.getIdentifier("third_party_license_metadata", "raw", context.getPackageName())); + String metadata = readStringAndClose(is, Integer.MAX_VALUE); + String[] lines = metadata.split("\n"); + List licenses = new ArrayList<>(lines.length); + for (String line : lines) { + int spaceIndex = line.indexOf(' '); + String[] position = line.substring(0, spaceIndex).split(":"); + if (spaceIndex <= 0 || position.length != 2) { + throw new IllegalStateException("Invalid license meta-data line:\n" + line); + } + licenses.add(new License(line.substring(spaceIndex + 1), Long.parseLong(position[0]), Integer.parseInt(position[1]), "")); + } + return licenses; + } + + public static String getLicenseText(Context context, License license) { + if (license.getPath().isEmpty()) { + Resources resources = context.getApplicationContext().getResources(); + InputStream is = resources.openRawResource(resources.getIdentifier("third_party_licenses", "raw", context.getPackageName())); + try { + if (is.skip(license.getOffset()) != license.getOffset()) { + throw new RuntimeException("Failed to read license"); + } + } catch (IOException e) { + throw new RuntimeException("Failed to read license", e); + } + return readStringAndClose(is, license.getLength()); + } else { + try (JarFile jar = new JarFile(license.getPath())) { + JarEntry entry = jar.getJarEntry("res/raw/third_party_licenses"); + if (entry == null) { + throw new RuntimeException(license.getPath() + " does not contain res/raw/third_party_licenses"); + } else { + InputStream is = jar.getInputStream(entry); + if (is.skip(license.getOffset()) != license.getOffset()) { + throw new RuntimeException("Failed to read license"); + } + return readStringAndClose(is, license.getLength()); + } + } catch (IOException e) { + throw new RuntimeException("Failed to read license", e); + } + } + } + + private static String readStringAndClose(InputStream is, int bytesToRead) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + byte[] bytes = new byte[1024]; + int read; + while (bytesToRead > 0 && (read = is.read(bytes, 0, Math.min(bytes.length, bytesToRead))) != -1) { + bos.write(bytes, 0, read); + bytesToRead -= read; + } + is.close(); + } catch (IOException e) { + throw new RuntimeException("Failed to read license or metadata", e); + } + try { + return bos.toString("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unsupported encoding UTF8. This should always be supported.", e); + } + } +} diff --git a/play-services-oss-licenses-core/build.gradle b/play-services-oss-licenses-core/build.gradle new file mode 100644 index 000000000..0a3b64320 --- /dev/null +++ b/play-services-oss-licenses-core/build.gradle @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-base-core') + api project(':play-services-oss-licenses-api') + api project(':play-services-oss-licenses') +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + sourceSets { + main { + java.srcDirs = ['src/main/kotlin'] + } + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-oss-licenses' diff --git a/play-services-oss-licenses-core/src/main/AndroidManifest.xml b/play-services-oss-licenses-core/src/main/AndroidManifest.xml new file mode 100644 index 000000000..916dba765 --- /dev/null +++ b/play-services-oss-licenses-core/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/play-services-oss-licenses-core/src/main/kotlin/org/microg/gms/oss/licenses/core/OssLicensesService.kt b/play-services-oss-licenses-core/src/main/kotlin/org/microg/gms/oss/licenses/core/OssLicensesService.kt new file mode 100644 index 000000000..db1a320d8 --- /dev/null +++ b/play-services-oss-licenses-core/src/main/kotlin/org/microg/gms/oss/licenses/core/OssLicensesService.kt @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.oss.licenses.core + +import android.content.Context +import com.google.android.gms.common.api.CommonStatusCodes.SUCCESS +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.oss.licenses.IOSSLicenseService +import com.google.android.gms.oss.licenses.License +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService + +private const val TAG = "OssLicensesService" + +class OssLicensesService : BaseService(TAG, GmsService.OSS_LICENSES) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService?) { + callback.onPostInitComplete(SUCCESS, OssLicensesServiceImpl(), null) + } +} + +class OssLicensesServiceImpl : IOSSLicenseService.Stub() { + + override fun getListLayoutPackage(packageName: String?): String? { + // Use fallback resources provided by package itself + return packageName + } + + override fun getLicenseLayoutPackage(packageName: String?): String? { + // Use fallback resources provided by package itself + return packageName + } + + override fun getLicenseDetail(libraryName: String?): String? { + // Use license provided by package itself + return null + } + + override fun getLicenseList(list: MutableList?): List { + // Just sort it + val newList = arrayListOf() + newList.addAll(list.orEmpty()) + newList.sortBy { it.name } + return newList + } + +} diff --git a/play-services-oss-licenses/build.gradle b/play-services-oss-licenses/build.gradle new file mode 100644 index 000000000..d7b91a591 --- /dev/null +++ b/play-services-oss-licenses/build.gradle @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG implementation of play-services-oss-licenses' + +dependencies { + api project(':play-services-oss-licenses-api') + + // Dependencies from play-services-oss-licenses:17.0.0 + api "androidx.loader:loader:1.0.0" + api project(':play-services-base') + api project(':play-services-basement') + api project(':play-services-tasks') + implementation "androidx.appcompat:appcompat:1.0.0" +} diff --git a/play-services-oss-licenses/src/main/AndroidManifest.xml b/play-services-oss-licenses/src/main/AndroidManifest.xml new file mode 100644 index 000000000..c3c640922 --- /dev/null +++ b/play-services-oss-licenses/src/main/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesActivity.java b/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesActivity.java new file mode 100644 index 000000000..c47ee92f0 --- /dev/null +++ b/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesActivity.java @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.oss.licenses; + +import android.content.res.Resources; +import android.os.Bundle; +import android.util.Log; +import android.view.MenuItem; +import android.widget.ScrollView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.oss.licenses.LicenseUtil; +import org.microg.gms.oss.licenses.R; + +/** + * An Activity used to display the actual content of a license in res/raw/third_party_licenses generated by oss + * licenses gradle plugin. This activity is invoked by list items from {@link OssLicensesMenuActivity}. + */ +@PublicApi +public class OssLicensesActivity extends AppCompatActivity { + private static final String SCROLL_POSITION_EXTRA = "scroll_pos"; + private License license; + private TextView textView; + private ScrollView scrollView; + private int restoredScrollPosition; + private boolean destroyed; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + destroyed = false; + license = getIntent().getParcelableExtra("license"); + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle(this.license.toString()); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setLogo(null); + } + OssLicensesServiceImpl service = new OssLicensesServiceImpl(this); + Task licenseDetailTask = service.getLicenseDetail(license); + Task licenseLayoutPackageTask = service.getLicenseLayoutPackage(getPackageName()); + Tasks.whenAll(licenseDetailTask, licenseLayoutPackageTask).addOnCompleteListener((nil) -> { + if (destroyed || isFinishing()) return; + + // Layout + String layoutPackage = getPackageName(); + if (licenseLayoutPackageTask.isSuccessful()) { + layoutPackage = licenseLayoutPackageTask.getResult(); + } + Resources resources; + try { + resources = getPackageManager().getResourcesForApplication(layoutPackage); + } catch (Exception e) { + layoutPackage = getPackageName(); + resources = getResources(); + } + setContentView(getLayoutInflater().inflate(resources.getXml(resources.getIdentifier("libraries_social_licenses_license_activity", "layout", layoutPackage)), null, false)); + textView = findViewById(resources.getIdentifier("license_activity_textview", "id", layoutPackage)); + scrollView = findViewById(resources.getIdentifier("license_activity_scrollview", "id", layoutPackage)); + + // Text + String licenseText = null; + if (licenseDetailTask.isSuccessful()) { + licenseText = licenseDetailTask.getResult(); + } + if (licenseText == null) { + try { + licenseText = LicenseUtil.getLicenseText(this, license); + } catch (Exception e) { + // Ignore + } + } + if (licenseText == null) { + textView.setText(R.string.license_content_error); + } else { + textView.setText(licenseText); + } + + // Restore scroll position + if (restoredScrollPosition != 0) { + scrollView.post(() -> scrollView.scrollTo(0, textView.getLayout().getLineTop(textView.getLayout().getLineForOffset(restoredScrollPosition)))); + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + destroyed = true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + restoredScrollPosition = savedInstanceState.getInt(SCROLL_POSITION_EXTRA); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + if (textView != null && scrollView != null) { + outState.putInt(SCROLL_POSITION_EXTRA, textView.getLayout().getLineStart(textView.getLayout().getLineForVertical(scrollView.getScrollY()))); + } + } +} diff --git a/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesMenuActivity.java b/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesMenuActivity.java new file mode 100644 index 000000000..ed58375b3 --- /dev/null +++ b/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesMenuActivity.java @@ -0,0 +1,211 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.oss.licenses; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.AsyncTaskLoader; +import androidx.loader.content.Loader; + +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.oss.licenses.LicenseUtil; +import org.microg.gms.oss.licenses.R; + +import java.util.List; + +/** + * An Activity used to display a list of all third party licenses in res/raw/third_party_license_metadata generated by + * oss licenses gradle plugin. Click on each item of the list would invoke {@link OssLicensesActivity} to show the + * actual content of the license. + */ +@PublicApi +public class OssLicensesMenuActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks> { + private static final String TAG = "OssLicensesMenuActivity"; + private static final String EXTRA_TITLE = "title"; + private static final int LOADER_ID = 54321; + private static String TITLE; + + private ListView listView; + private boolean destroyed; + private ArrayAdapter licensesAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + destroyed = false; + if (TITLE == null) { + Intent intent = getIntent(); + if (intent != null && intent.hasExtra(EXTRA_TITLE)) { + TITLE = intent.getStringExtra(EXTRA_TITLE); + Log.w(TAG, "The intent based title is deprecated. Use OssLicensesMenuActivity.setActivityTitle(title) instead."); + } + } + if (TITLE != null) { + setTitle(TITLE); + } + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + if (LicenseUtil.hasLicenses(this)) { + OssLicensesServiceImpl service = new OssLicensesServiceImpl(this); + service.getListLayoutPackage(getPackageName()).addOnCompleteListener((layoutPackageTask) -> { + if (destroyed || isFinishing()) return; + + // Layout + String layoutPackage = getPackageName(); + if (layoutPackageTask.isSuccessful()) { + layoutPackage = layoutPackageTask.getResult(); + } + Resources resources; + try { + resources = getPackageManager().getResourcesForApplication(layoutPackage); + } catch (Exception e) { + layoutPackage = getPackageName(); + resources = getResources(); + } + setContentView(getLayoutInflater().inflate(resources.getXml(resources.getIdentifier("libraries_social_licenses_license_menu_activity", "layout", layoutPackage)), null, false)); + licensesAdapter = new LicensesAdapter(this, getLayoutInflater(), resources, layoutPackage); + listView = findViewById(resources.getIdentifier("license_list", "id", layoutPackage)); + listView.setAdapter(licensesAdapter); + listView.setOnItemClickListener((parent, view, position, id) -> { + License license = (License) parent.getItemAtPosition(position); + Intent intent = new Intent(this, OssLicensesActivity.class); + intent.putExtra("license", license); + startActivity(intent); + }); + }); + LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this); + } else { + setContentView(R.layout.license_menu_activity_no_licenses); + } + } + + private static class LicensesAdapter extends ArrayAdapter { + private final LayoutInflater layoutInflater; + private final Resources resources; + private final String layoutPackage; + + public LicensesAdapter(@NonNull Context context, @NonNull LayoutInflater layoutInflater, @NonNull Resources resources, @NonNull String layoutPackage) { + super(context, 0); + this.layoutInflater = layoutInflater; + this.resources = resources; + this.layoutPackage = layoutPackage; + } + + @NonNull + @Override + public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) { + if (view == null) { + view = layoutInflater.inflate(resources.getXml(resources.getIdentifier("libraries_social_licenses_license", "layout", layoutPackage)), null, false); + } + TextView textView = view.findViewById(resources.getIdentifier("license", "id", layoutPackage)); + textView.setText(getItem(position).toString()); + return view; + } + } + + @NonNull + @Override + public Loader> onCreateLoader(int id, @Nullable Bundle args) { + return new AsyncTaskLoader>(getApplicationContext()) { + private List storedData; + + @Nullable + @Override + public List loadInBackground() { + List licenses = LicenseUtil.getLicensesFromMetadata(getContext()); + try { + OssLicensesServiceImpl service = new OssLicensesServiceImpl(getContext()); + Task> licensesTask = service.getLicenseList(licenses); + return Tasks.await(licensesTask); + } catch (Exception e) { + Log.w(TAG, "Error getting license list from service.", e); + } + return licenses; + } + + @Override + public void deliverResult(@Nullable List data) { + this.storedData = data; + super.deliverResult(data); + } + + @Override + protected void onStartLoading() { + if (storedData != null) { + deliverResult(storedData); + } else { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + }; + } + + @Override + protected void onDestroy() { + destroyed = true; + LoaderManager.getInstance(this).destroyLoader(LOADER_ID); + super.onDestroy(); + } + + @Override + public void onLoadFinished(@NonNull Loader> loader, List data) { + licensesAdapter.clear(); + licensesAdapter.addAll(data); + licensesAdapter.notifyDataSetChanged(); + } + + @Override + public void onLoaderReset(@NonNull Loader> loader) { + licensesAdapter.clear(); + licensesAdapter.notifyDataSetChanged(); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** + * Sets the title for {@link OssLicensesMenuActivity}. + * + * @param title the title for this activity + */ + public static void setActivityTitle(String title) { + OssLicensesMenuActivity.TITLE = title; + } +} diff --git a/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesServiceImpl.java b/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesServiceImpl.java new file mode 100644 index 000000000..223033b49 --- /dev/null +++ b/play-services-oss-licenses/src/main/java/com/google/android/gms/oss/licenses/OssLicensesServiceImpl.java @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.oss.licenses; + +import android.content.Context; +import android.os.RemoteException; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApi; +import com.google.android.gms.tasks.Task; + +import org.microg.gms.common.api.PendingGoogleApiCall; +import org.microg.gms.oss.licenses.OssLicenseServiceApiClient; + +import java.util.List; + +public class OssLicensesServiceImpl extends GoogleApi { + private static final Api API = new Api<>((options, context, looper, clientSettings, callbacks, connectionFailedListener) -> new OssLicenseServiceApiClient(context, callbacks, connectionFailedListener)); + + public OssLicensesServiceImpl(Context context) { + super(context, API); + } + + public Task getLicenseLayoutPackage(String packageName) { + return scheduleTask((PendingGoogleApiCall) (client, completionSource) -> { + String result; + try { + result = client.getLicenseLayoutPackage(packageName); + } catch (RemoteException e) { + completionSource.setException(e); + return; + } + completionSource.setResult(result); + }); + } + + public Task getListLayoutPackage(String packageName) { + return scheduleTask((PendingGoogleApiCall) (client, completionSource) -> { + String result; + try { + result = client.getListLayoutPackage(packageName); + } catch (RemoteException e) { + completionSource.setException(e); + return; + } + completionSource.setResult(result); + }); + } + + public Task getLicenseDetail(License license) { + return scheduleTask((PendingGoogleApiCall) (client, completionSource) -> { + String result; + try { + result = client.getLicenseDetail(license); + } catch (RemoteException e) { + completionSource.setException(e); + return; + } + completionSource.setResult(result); + }); + } + + public Task> getLicenseList(List licenses) { + return scheduleTask((PendingGoogleApiCall, OssLicenseServiceApiClient>) (client, completionSource) -> { + List result; + try { + result = client.getLicenseList(licenses); + } catch (RemoteException e) { + completionSource.setException(e); + return; + } + completionSource.setResult(result); + }); + } +} diff --git a/play-services-oss-licenses/src/main/java/org/microg/gms/oss/licenses/OssLicenseServiceApiClient.java b/play-services-oss-licenses/src/main/java/org/microg/gms/oss/licenses/OssLicenseServiceApiClient.java new file mode 100644 index 000000000..d2e89e025 --- /dev/null +++ b/play-services-oss-licenses/src/main/java/org/microg/gms/oss/licenses/OssLicenseServiceApiClient.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.oss.licenses; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; + +import com.google.android.gms.oss.licenses.IOSSLicenseService; +import com.google.android.gms.oss.licenses.License; + +import org.microg.gms.common.GmsClient; +import org.microg.gms.common.GmsService; +import org.microg.gms.common.api.ConnectionCallbacks; +import org.microg.gms.common.api.OnConnectionFailedListener; + +import java.util.List; + +public class OssLicenseServiceApiClient extends GmsClient { + public OssLicenseServiceApiClient(Context context, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) { + super(context, callbacks, connectionFailedListener, GmsService.OSS_LICENSES.ACTION); + serviceId = GmsService.OSS_LICENSES.SERVICE_ID; + } + + public String getLicenseLayoutPackage(String packageName) throws RemoteException { + return getServiceInterface().getLicenseLayoutPackage(packageName); + } + + public String getListLayoutPackage(String packageName) throws RemoteException { + return getServiceInterface().getListLayoutPackage(packageName); + } + + public String getLicenseDetail(License license) throws RemoteException { + return getServiceInterface().getLicenseDetail(license.toString()); + } + + public List getLicenseList(List licenses) throws RemoteException { + return getServiceInterface().getLicenseList(licenses); + } + + @Override + protected IOSSLicenseService interfaceFromBinder(IBinder binder) { + return IOSSLicenseService.Stub.asInterface(binder); + } +} diff --git a/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license.xml b/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license.xml new file mode 100644 index 000000000..6e22ed10e --- /dev/null +++ b/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license_activity.xml b/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license_activity.xml new file mode 100644 index 000000000..02975e686 --- /dev/null +++ b/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license_activity.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license_menu_activity.xml b/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license_menu_activity.xml new file mode 100644 index 000000000..2b3886dd4 --- /dev/null +++ b/play-services-oss-licenses/src/main/res/layout/libraries_social_licenses_license_menu_activity.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/play-services-oss-licenses/src/main/res/layout/license_menu_activity_no_licenses.xml b/play-services-oss-licenses/src/main/res/layout/license_menu_activity_no_licenses.xml new file mode 100644 index 000000000..2bed2bcea --- /dev/null +++ b/play-services-oss-licenses/src/main/res/layout/license_menu_activity_no_licenses.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/play-services-oss-licenses/src/main/res/values/strings.xml b/play-services-oss-licenses/src/main/res/values/strings.xml new file mode 100644 index 000000000..0aea7bf85 --- /dev/null +++ b/play-services-oss-licenses/src/main/res/values/strings.xml @@ -0,0 +1,13 @@ + + + + An error has occurred when fetching the license. + License info is loading. + License list is loading. + This app does not have any open source licenses. + Open source licenses + License details for open source software + diff --git a/settings.gradle b/settings.gradle index 1d0661178..c4b1c97c8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ include ':play-services-droidguard-api' include ':play-services-iid-api' include ':play-services-location-api' include ':play-services-nearby-api' +include ':play-services-oss-licenses-api' include ':play-services-safetynet-api' include ':play-services-tapandpay-api' include ':play-services-vision-api' @@ -25,6 +26,7 @@ include ':play-services-gcm' include ':play-services-iid' include ':play-services-location' include ':play-services-nearby' +include ':play-services-oss-licenses' include ':play-services-vision' include ':play-services-vision-common' include ':play-services-wearable' @@ -55,6 +57,7 @@ include ':play-services-maps-core-mapbox' include ':play-services-maps-core-vtm' include ':play-services-maps-core-vtm:vtm-microg-theme' include ':play-services-nearby-core' +include ':play-services-oss-licenses-core' include ':play-services-safetynet-core' include ':play-services-tapandpay-core' include ':play-services-vision-core' -- GitLab From 06fdbc34a2fa8e501eb65b833c90161efee019d6 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 21 Mar 2022 11:15:03 -0600 Subject: [PATCH 08/26] Add more API details of phenotype API --- .../android/gms/phenotype/DogfoodsToken.aidl | 3 + .../gms/phenotype/ExperimentTokens.aidl | 3 + .../google/android/gms/phenotype/Flag.aidl | 3 + .../android/gms/phenotype/FlagOverrides.aidl | 3 + .../gms/phenotype/RegistrationInfo.aidl | 3 + .../internal/IPhenotypeCallbacks.aidl | 22 +++- .../phenotype/internal/IPhenotypeService.aidl | 28 ++++- .../android/gms/phenotype/Configuration.java | 6 +- .../android/gms/phenotype/DogfoodsToken.java | 12 ++ .../gms/phenotype/ExperimentTokens.java | 30 +++++ .../google/android/gms/phenotype/Flag.java | 90 +++++++++++++ .../android/gms/phenotype/FlagOverrides.java | 12 ++ .../gms/phenotype/RegistrationInfo.java | 27 ++++ .../microg/gms/phenotype/PhenotypeService.kt | 119 ++++++++++++++++-- 14 files changed, 342 insertions(+), 19 deletions(-) create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/phenotype/DogfoodsToken.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/phenotype/ExperimentTokens.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/phenotype/Flag.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/phenotype/FlagOverrides.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/phenotype/RegistrationInfo.aidl create mode 100644 play-services-api/src/main/java/com/google/android/gms/phenotype/DogfoodsToken.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentTokens.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/phenotype/FlagOverrides.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/phenotype/RegistrationInfo.java diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/DogfoodsToken.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/DogfoodsToken.aidl new file mode 100644 index 000000000..991fca0f7 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/DogfoodsToken.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable DogfoodsToken; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/ExperimentTokens.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/ExperimentTokens.aidl new file mode 100644 index 000000000..4b6ba4f80 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/ExperimentTokens.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable ExperimentTokens; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/Flag.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/Flag.aidl new file mode 100644 index 000000000..f91175f8a --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/Flag.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable Flag; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/FlagOverrides.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/FlagOverrides.aidl new file mode 100644 index 000000000..bdf15a656 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/FlagOverrides.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable FlagOverrides; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/RegistrationInfo.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/RegistrationInfo.aidl new file mode 100644 index 000000000..644ba2d81 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/RegistrationInfo.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable RegistrationInfo; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeCallbacks.aidl index acda3c576..3c29b8b6e 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeCallbacks.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeCallbacks.aidl @@ -2,8 +2,26 @@ package com.google.android.gms.phenotype.internal; import com.google.android.gms.common.api.Status; import com.google.android.gms.phenotype.Configurations; +import com.google.android.gms.phenotype.DogfoodsToken; +import com.google.android.gms.phenotype.ExperimentTokens; +import com.google.android.gms.phenotype.Flag; +import com.google.android.gms.phenotype.FlagOverrides; interface IPhenotypeCallbacks { - oneway void onRegister(in Status status) = 0; - oneway void onConfigurations(in Status status, in Configurations configurations) = 3; + oneway void onRegistered(in Status status) = 0; + oneway void onWeakRegistered(in Status status) = 1; + oneway void onUnregistered(in Status status) = 2; + oneway void onConfiguration(in Status status, in Configurations configurations) = 3; + oneway void onCommitedToConfiguration(in Status status) = 4; + oneway void onExperimentTokens(in Status status, in ExperimentTokens experimentTokens) = 5; + oneway void onDogfoodsToken(in Status status, in DogfoodsToken dogfoodsToken) = 6; + oneway void onDogfoodsTokenSet(in Status status) = 7; + oneway void onFlag(in Status status, in Flag flag) = 8; + oneway void onCommittedConfiguration(in Status status, in Configurations configuration) = 9; + oneway void onSyncFinished(in Status status, long p1) = 10; + oneway void onFlagOverridesSet(in Status status) = 11; + oneway void onFlagOverrides(in Status status, in FlagOverrides overrides) = 12; + oneway void onAppSpecificPropertiesSet(in Status status) = 13; + + oneway void onServingVersion(in Status status, long version) = 15; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl index abba647db..77d315b74 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl @@ -1,9 +1,31 @@ package com.google.android.gms.phenotype.internal; import com.google.android.gms.phenotype.internal.IPhenotypeCallbacks; +import com.google.android.gms.phenotype.Flag; +import com.google.android.gms.phenotype.RegistrationInfo; interface IPhenotypeService { - void register(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in byte[] p4) = 0; - void register2(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in int[] p4, in byte[] p5) = 1; - void getConfigurationSnapshot(IPhenotypeCallbacks callbacks, String p1, String p2, String p3) = 10; + oneway void register(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in byte[] p4) = 0; // returns via callbacks.onRegistered() + oneway void weakRegister(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in int[] p4, in byte[] p5) = 1; // returns via callbacks.onWeakRegistered() + oneway void unregister(IPhenotypeCallbacks callbacks, String p1) = 2; // returns via callbacks.onUnregistered() + oneway void getConfigurationSnapshot(IPhenotypeCallbacks callbacks, String p1, String p2) = 3; // returns via callbacks.onConfiguration() + oneway void commitToConfiguration(IPhenotypeCallbacks callbacks, String p1) = 4; // returns via callbacks.onCommitedToConfiguration() + oneway void getExperimentTokens(IPhenotypeCallbacks callbacks, String p1, String logSourceName) = 5; // returns via callbacks.onExperimentTokens() + oneway void getDogfoodsToken(IPhenotypeCallbacks callbacks) = 6; // returns via callbacks.onDogfoodsToken() + oneway void setDogfoodsToken(IPhenotypeCallbacks callbacks, in byte[] p1) = 7; // returns via callbacks.onDogfoodsTokenSet() + oneway void getFlag(IPhenotypeCallbacks callbacks, String packageName, String name, int type) = 8; // returns via callbacks.onFlag() + oneway void getCommitedConfiguration(IPhenotypeCallbacks callbacks, String p1) = 9; // returns via callbacks.onCommittedConfiguration() + oneway void getConfigurationSnapshot2(IPhenotypeCallbacks callbacks, String p1, String p2, String p3) = 10; // returns via callbacks.onConfiguration() + oneway void syncAfterOperation(IPhenotypeCallbacks callbacks, String p1, long p2) = 11; // returns via callbacks.onSyncFinished() + oneway void registerSync(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in byte[] p4, String p5, String p6) = 12; // returns via callbacks.onConfiguration() + oneway void setFlagOverrides(IPhenotypeCallbacks callbacks, String packageName, String user, String flagName, int flagType, int flagDataType, String flagValue) = 13; // returns via callbacks.onFlagOverridesSet() + oneway void deleteFlagOverrides(IPhenotypeCallbacks callbacks, String packageName, String user, String flagName) = 14; // returns via callbacks.onFlagOverrides() + oneway void listFlagOverrides(IPhenotypeCallbacks callbacks, String packageName, String user, String flagName) = 15; // returns via callbacks.onFlagOverrides() + + oneway void clearFlagOverrides(IPhenotypeCallbacks callbacks, String packageName, String user) = 17; // returns via callbacks.onFlagOverridesSet() + oneway void bulkRegister(IPhenotypeCallbacks callbacks, in RegistrationInfo[] infos) = 18; // returns via callbacks.onRegister() + oneway void setAppSpecificProperties(IPhenotypeCallbacks callbacks, String p1, in byte[] p2) = 19; // returns via callbacks.onAppSpecificPropertiesSet() + + oneway void getServingVersion(IPhenotypeCallbacks callbacks) = 21; // returns via callbacks.onServingVersion() + oneway void getExperimentTokens2(IPhenotypeCallbacks callbacks, String p1, String p2, String p3, String p4) = 22; // returns via callbacks.onExperimentTokens() } diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/Configuration.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/Configuration.java index 20bb9d4cd..2433df227 100644 --- a/play-services-api/src/main/java/com/google/android/gms/phenotype/Configuration.java +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/Configuration.java @@ -9,10 +9,10 @@ import org.microg.safeparcel.AutoSafeParcelable; public class Configuration extends AutoSafeParcelable { @Field(2) - public int field2; + public int flagType; @Field(3) - public Flag[] field3; + public Flag[] flags; @Field(4) - public String[] field4; + public String[] names; public static final Creator CREATOR = new AutoCreator<>(Configuration.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/DogfoodsToken.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/DogfoodsToken.java new file mode 100644 index 000000000..917d37570 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/DogfoodsToken.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.phenotype; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class DogfoodsToken extends AutoSafeParcelable { + public static final Creator CREATOR = new AutoCreator<>(DogfoodsToken.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentTokens.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentTokens.java new file mode 100644 index 000000000..d3f858fec --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentTokens.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.phenotype; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class ExperimentTokens extends AutoSafeParcelable { + @Field(2) + public String field2; + @Field(3) + public byte[] direct; + @Field(4) + public byte[][] gaia; + @Field(5) + public byte[][] pseudo; + @Field(6) + public byte[][] always; + @Field(7) + public byte[][] other; + @Field(8) + public int[] weak; + @Field(9) + public byte[][] directs; + @Field(10) + public int[] genericDimensions; + public static final Creator CREATOR = new AutoCreator<>(ExperimentTokens.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/Flag.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/Flag.java index 0c1fdc8aa..4d0a03af7 100644 --- a/play-services-api/src/main/java/com/google/android/gms/phenotype/Flag.java +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/Flag.java @@ -8,5 +8,95 @@ package com.google.android.gms.phenotype; import org.microg.safeparcel.AutoSafeParcelable; public class Flag extends AutoSafeParcelable { + @Field(2) + public String name; + @Field(3) + private long longValue; + @Field(4) + private boolean boolValue; + @Field(5) + private double doubleValue; + @Field(6) + private String stringValue; + @Field(7) + private byte[] bytesValue; + @Field(8) + public int dataType; + @Field(9) + public int flagType; + + private Flag() { + } + + public Flag(String name, long longValue, int flagType) { + this.name = name; + this.longValue = longValue; + this.dataType = DATA_TYPE_LONG; + this.flagType = flagType; + } + + public Flag(String name, boolean boolValue, int flagType) { + this.name = name; + this.boolValue = boolValue; + this.dataType = DATA_TYPE_BOOL; + this.flagType = flagType; + } + + public Flag(String name, double doubleValue, int flagType) { + this.name = name; + this.doubleValue = doubleValue; + this.dataType = DATA_TYPE_DOUBLE; + this.flagType = flagType; + } + + public Flag(String name, String stringValue, int flagType) { + this.name = name; + this.stringValue = stringValue; + this.dataType = DATA_TYPE_STRING; + this.flagType = flagType; + } + + public Flag(String name, byte[] bytesValue, int flagType) { + this.name = name; + this.bytesValue = bytesValue; + this.dataType = DATA_TYPE_BYTES; + this.flagType = flagType; + } + + public long getLong() { + if (dataType == DATA_TYPE_LONG) + return longValue; + throw new IllegalArgumentException("Not a long type"); + } + + public boolean getBool() { + if (dataType == DATA_TYPE_BOOL) + return boolValue; + throw new IllegalArgumentException("Not a boolean type"); + } + + public double getDouble() { + if (dataType == DATA_TYPE_DOUBLE) + return doubleValue; + throw new IllegalArgumentException("Not a double type"); + } + + public String getString() { + if (dataType == DATA_TYPE_STRING) + return stringValue; + throw new IllegalArgumentException("Not a String type"); + } + + public byte[] getBytes() { + if (dataType == DATA_TYPE_BYTES) + return bytesValue; + throw new IllegalArgumentException("Not a bytes type"); + } + + public static final int DATA_TYPE_LONG = 1; + public static final int DATA_TYPE_BOOL = 2; + public static final int DATA_TYPE_DOUBLE = 3; + public static final int DATA_TYPE_STRING = 4; + public static final int DATA_TYPE_BYTES = 5; public static final Creator CREATOR = new AutoCreator<>(Flag.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/FlagOverrides.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/FlagOverrides.java new file mode 100644 index 000000000..dca121693 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/FlagOverrides.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.phenotype; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class FlagOverrides extends AutoSafeParcelable { + public static final Creator CREATOR = new AutoCreator<>(FlagOverrides.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/RegistrationInfo.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/RegistrationInfo.java new file mode 100644 index 000000000..d36733c05 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/RegistrationInfo.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.phenotype; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class RegistrationInfo extends AutoSafeParcelable { + @Field(1) + public String field1; + @Field(2) + public int field2; + @Field(3) + public String[] field3; + @Field(4) + public byte[] field4; + @Field(5) + public boolean field5; + @Field(6) + public int[] field6; + @Field(7) + public String field7; + + public static final Creator CREATOR = new AutoCreator<>(RegistrationInfo.class); +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt b/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt index ec83c6816..332cae409 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-FileCopyrightText: 2020 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ @@ -10,14 +10,14 @@ import android.util.Log import com.google.android.gms.common.api.Status import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.IGmsCallbacks -import com.google.android.gms.phenotype.Configurations +import com.google.android.gms.phenotype.* import com.google.android.gms.phenotype.internal.IPhenotypeCallbacks import com.google.android.gms.phenotype.internal.IPhenotypeService import org.microg.gms.BaseService import org.microg.gms.common.GmsService import org.microg.gms.utils.warnOnTransactionIssues -private const val TAG = "GmsPhenotypeSvc" +private const val TAG = "PhenotypeService" class PhenotypeService : BaseService(TAG, GmsService.PHENOTYPE) { override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest?, service: GmsService?) { @@ -28,20 +28,117 @@ class PhenotypeService : BaseService(TAG, GmsService.PHENOTYPE) { class PhenotypeServiceImpl : IPhenotypeService.Stub() { override fun register(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: ByteArray?) { Log.d(TAG, "register($p1, $p2, $p3, $p4)") - callbacks.onRegister(Status.SUCCESS) + callbacks.onRegistered(Status.SUCCESS) } - override fun register2(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: IntArray?, p5: ByteArray?) { - Log.d(TAG, "register2($p1, $p2, $p3, $p4, $p5)") - callbacks.onRegister(Status.SUCCESS) + override fun weakRegister(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: IntArray?, p5: ByteArray?) { + Log.d(TAG, "weakRegister($p1, $p2, $p3, $p4, $p5)") + callbacks.onWeakRegistered(Status.SUCCESS) } - override fun getConfigurationSnapshot(callbacks: IPhenotypeCallbacks, p1: String?, p2: String?, p3: String?) { - Log.d(TAG, "getConfigurationSnapshot($p1, $p2, $p3)") - callbacks.onConfigurations(Status.SUCCESS, Configurations().apply { + override fun unregister(callbacks: IPhenotypeCallbacks, p1: String?) { + Log.d(TAG, "unregister($p1)") + callbacks.onUnregistered(Status.SUCCESS) + } + + override fun getConfigurationSnapshot(callbacks: IPhenotypeCallbacks, p1: String?, p2: String?) { + Log.d(TAG, "getConfigurationSnapshot($p1, $p2)") + callbacks.onConfiguration(Status.SUCCESS, Configurations().apply { + field4 = emptyArray() + }) + } + + override fun commitToConfiguration(callbacks: IPhenotypeCallbacks, p1: String?) { + Log.d(TAG, "commitToConfiguration($p1)") + callbacks.onCommitedToConfiguration(Status.SUCCESS) + } + + override fun getExperimentTokens(callbacks: IPhenotypeCallbacks, p1: String?, logSourceName: String?) { + Log.d(TAG, "getExperimentTokens($p1, $logSourceName)") + callbacks.onExperimentTokens(Status.SUCCESS, ExperimentTokens()) + } + + override fun getDogfoodsToken(callbacks: IPhenotypeCallbacks) { + Log.d(TAG, "getDogfoodsToken()") + callbacks.onDogfoodsToken(Status.SUCCESS, DogfoodsToken()) + } + + override fun setDogfoodsToken(callbacks: IPhenotypeCallbacks, p1: ByteArray?) { + Log.d(TAG, "setDogfoodsToken($p1)") + callbacks.onDogfoodsTokenSet(Status.SUCCESS) + } + + override fun getFlag(callbacks: IPhenotypeCallbacks, packageName: String?, name: String?, type: Int) { + Log.d(TAG, "setDogfoodsToken($packageName, $name, $type)") + callbacks.onFlag(Status.SUCCESS, null) + } + + override fun getCommitedConfiguration(callbacks: IPhenotypeCallbacks, p1: String?) { + Log.d(TAG, "getCommitedConfiguration($p1)") + callbacks.onCommittedConfiguration(Status.SUCCESS, Configurations().apply { + field4 = emptyArray() + }) + } + + override fun getConfigurationSnapshot2(callbacks: IPhenotypeCallbacks, p1: String?, p2: String?, p3: String?) { + Log.d(TAG, "getConfigurationSnapshot2($p1, $p2, $p3)") + callbacks.onConfiguration(Status.SUCCESS, Configurations().apply { + field4 = emptyArray() + }) + } + + override fun syncAfterOperation(callbacks: IPhenotypeCallbacks, p1: String?, p2: Long) { + Log.d(TAG, "syncAfterOperation($p1, $p2)") + callbacks.onSyncFinished(Status.SUCCESS, p2) + } + + override fun registerSync(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: ByteArray?, p5: String?, p6: String?) { + Log.d(TAG, "registerSync($p1, $p2, $p3, $p4, $p5, $p6)") + callbacks.onConfiguration(Status.SUCCESS, Configurations().apply { field4 = emptyArray() }) } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } + override fun setFlagOverrides(callbacks: IPhenotypeCallbacks, packageName: String?, user: String?, flagName: String?, flagType: Int, flagDataType: Int, flagValue: String?) { + Log.d(TAG, "setFlagOverrides($packageName, $user, $flagName, $flagDataType, $flagType, $flagDataType, $flagValue)") + callbacks.onFlagOverridesSet(Status.SUCCESS) + } + + override fun deleteFlagOverrides(callbacks: IPhenotypeCallbacks, packageName: String?, user: String?, flagName: String?) { + Log.d(TAG, "deleteFlagOverrides($packageName, $user, $flagName)") + callbacks.onFlagOverrides(Status.SUCCESS, FlagOverrides()) + } + + override fun listFlagOverrides(callbacks: IPhenotypeCallbacks, packageName: String?, user: String?, flagName: String?) { + Log.d(TAG, "listFlagOverrides($packageName, $user, $flagName)") + callbacks.onFlagOverrides(Status.SUCCESS, FlagOverrides()) + } + + override fun clearFlagOverrides(callbacks: IPhenotypeCallbacks, packageName: String?, user: String?) { + Log.d(TAG, "clearFlagOverrides($packageName, $user)") + callbacks.onFlagOverridesSet(Status.SUCCESS) + } + + override fun bulkRegister(callbacks: IPhenotypeCallbacks, infos: Array?) { + Log.d(TAG, "bulkRegister($infos)") + callbacks.onRegistered(Status.SUCCESS) + } + + override fun setAppSpecificProperties(callbacks: IPhenotypeCallbacks, p1: String?, p2: ByteArray?) { + Log.d(TAG, "setAppSpecificProperties($p1, $p2)") + callbacks.onAppSpecificPropertiesSet(Status.SUCCESS) + } + + override fun getServingVersion(callbacks: IPhenotypeCallbacks) { + Log.d(TAG, "getServingVersion()") + callbacks.onServingVersion(Status.SUCCESS, 1) + } + + override fun getExperimentTokens2(callbacks: IPhenotypeCallbacks, p1: String?, p2: String?, p3: String?, p4: String?) { + Log.d(TAG, "getExperimentTokens2($p1, $p2, $p3, $p4)") + callbacks.onExperimentTokens(Status.SUCCESS, ExperimentTokens()) + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } } -- GitLab From d5a01d9ea3106e93d0baec0fbcafc683ea0b9ee0 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 19 Mar 2022 12:26:13 +0100 Subject: [PATCH 09/26] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ffcefc7b9..f9136e056 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ microG GmsCore is a FLOSS (Free/Libre Open Source Software) framework to allow a License ------- - Copyright 2013-2021 microG Project Team + Copyright 2013-2022 microG Project Team Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- GitLab From 800ccc1ed4eefc006c35c95a4641bd29329bfd60 Mon Sep 17 00:00:00 2001 From: Fynn Godau Date: Mon, 18 Apr 2022 14:53:27 +0200 Subject: [PATCH 10/26] Add explicit exported value --- play-services-nearby-core/src/main/AndroidManifest.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/play-services-nearby-core/src/main/AndroidManifest.xml b/play-services-nearby-core/src/main/AndroidManifest.xml index a9f717dc8..f5e237928 100644 --- a/play-services-nearby-core/src/main/AndroidManifest.xml +++ b/play-services-nearby-core/src/main/AndroidManifest.xml @@ -37,7 +37,8 @@ + android:process=":persistent" + android:exported="true"> @@ -45,7 +46,8 @@ + android:process=":persistent" + android:exported="true"> -- GitLab From 32199c8125935f38d69e30e72580cdb7c90c604c Mon Sep 17 00:00:00 2001 From: Fynn Godau Date: Tue, 19 Apr 2022 12:32:13 +0200 Subject: [PATCH 11/26] Add FLAG_IMMUTABLE to PendingIntents --- .../gms/nearby/exposurenotification/AdvertiserService.kt | 3 ++- .../microg/gms/nearby/exposurenotification/CleanupService.kt | 5 ++++- .../exposurenotification/ExposureNotificationServiceImpl.kt | 2 +- .../microg/gms/nearby/exposurenotification/NotifyService.kt | 4 +++- .../microg/gms/nearby/exposurenotification/ScannerService.kt | 5 ++++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt index 6b8946423..3c54678ca 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt @@ -8,6 +8,7 @@ package org.microg.gms.nearby.exposurenotification import android.annotation.TargetApi import android.app.AlarmManager import android.app.PendingIntent +import android.app.PendingIntent.FLAG_IMMUTABLE import android.app.PendingIntent.FLAG_ONE_SHOT import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.bluetooth.BluetoothAdapter.* @@ -196,7 +197,7 @@ class AdvertiserService : LifecycleService() { private fun scheduleRestartAdvertising(nextSend: Long) { val intent = Intent(this, AdvertiserService::class.java).apply { action = ACTION_RESTART_ADVERTISING } - val pendingIntent = PendingIntent.getService(this, ACTION_RESTART_ADVERTISING.hashCode(), intent, FLAG_ONE_SHOT and FLAG_UPDATE_CURRENT) + val pendingIntent = PendingIntent.getService(this, ACTION_RESTART_ADVERTISING.hashCode(), intent, FLAG_ONE_SHOT or FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) when { Build.VERSION.SDK_INT >= 23 -> alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + nextSend, pendingIntent) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt index 95804b97d..c313e91ff 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/CleanupService.kt @@ -7,6 +7,9 @@ package org.microg.gms.nearby.exposurenotification import android.app.AlarmManager import android.app.PendingIntent +import android.app.PendingIntent.FLAG_IMMUTABLE +import android.app.PendingIntent.FLAG_ONE_SHOT +import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.Context import android.content.Intent import android.util.Log @@ -47,7 +50,7 @@ class CleanupService : LifecycleService() { fun stop() { val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager - val pendingIntent = PendingIntent.getService(applicationContext, CleanupService::class.java.name.hashCode(), Intent(applicationContext, CleanupService::class.java), PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT) + val pendingIntent = PendingIntent.getService(applicationContext, CleanupService::class.java.name.hashCode(), Intent(applicationContext, CleanupService::class.java), FLAG_ONE_SHOT or FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) alarmManager.set(AlarmManager.RTC, ExposurePreferences(this).lastCleanup + CLEANUP_INTERVAL, pendingIntent) stopSelf() } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 6e963ae9f..e4ebabaa1 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -106,7 +106,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val Log.w(TAG, e) } Log.d(TAG, "Pending: $intent") - val pi = PendingIntent.getActivity(context, permission.hashCode(), intent, PendingIntent.FLAG_ONE_SHOT) + val pi = PendingIntent.getActivity(context, permission.hashCode(), intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE) Log.d(TAG, "Pending: $pi") return pi } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt index 0245752a8..857738ced 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt @@ -7,6 +7,8 @@ package org.microg.gms.nearby.exposurenotification import android.annotation.TargetApi import android.app.* +import android.app.PendingIntent.FLAG_IMMUTABLE +import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.bluetooth.BluetoothAdapter import android.content.BroadcastReceiver import android.content.Context @@ -96,7 +98,7 @@ class NotifyService : LifecycleService() { try { val intent = Intent(Constants.ACTION_EXPOSURE_NOTIFICATION_SETTINGS).apply { `package` = packageName } intent.resolveActivity(packageManager) - setContentIntent(PendingIntent.getActivity(this@NotifyService, notificationId, Intent(Constants.ACTION_EXPOSURE_NOTIFICATION_SETTINGS).apply { `package` = packageName }, PendingIntent.FLAG_UPDATE_CURRENT)) + setContentIntent(PendingIntent.getActivity(this@NotifyService, notificationId, Intent(Constants.ACTION_EXPOSURE_NOTIFICATION_SETTINGS).apply { `package` = packageName }, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)) } catch (e: Exception) { // Ignore } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index 9ee699e0d..04e0c1469 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -9,6 +9,9 @@ import android.annotation.SuppressLint import android.annotation.TargetApi import android.app.AlarmManager import android.app.PendingIntent +import android.app.PendingIntent.FLAG_IMMUTABLE +import android.app.PendingIntent.FLAG_ONE_SHOT +import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.bluetooth.BluetoothAdapter.* import android.bluetooth.le.* import android.content.BroadcastReceiver @@ -153,7 +156,7 @@ class ScannerService : LifecycleService() { private fun scheduleStartScan(nextScan: Long) { val intent = Intent(this, ScannerService::class.java) - val pendingIntent = PendingIntent.getService(this, ScannerService::class.java.hashCode(), intent, PendingIntent.FLAG_ONE_SHOT and PendingIntent.FLAG_UPDATE_CURRENT) + val pendingIntent = PendingIntent.getService(this, ScannerService::class.java.hashCode(), intent, FLAG_ONE_SHOT or FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) if (Build.VERSION.SDK_INT >= 23) { // Note: there is no setWindowAndAllowWhileIdle() alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + nextScan, pendingIntent) -- GitLab From 3e733135059a71550ff00ccc61588c028260856f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 3 Mar 2022 11:59:32 -0300 Subject: [PATCH 12/26] Use new method for lifting idle restrictions temporarily when receiving PUSH messages The new method only gets used on Android 12 and above. https: //cs.android.com/android/_/android/platform/frameworks/base/+/1aa89de1cfec5b7c1197d2f6c2b33dffda8bd3f5 Change-Id: Iaa501a862de061ecf78aef25d66e35ed94104406 --- .../java/org/microg/gms/gcm/McsService.java | 61 ++++++++++++++----- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 5a9eb5341..8dad5fc9e 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -16,6 +16,7 @@ package org.microg.gms.gcm; +import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; @@ -39,6 +40,8 @@ import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.legacy.content.WakefulBroadcastReceiver; import com.google.android.gms.R; @@ -155,9 +158,18 @@ public class McsService extends Service implements Handler.Callback { private static int maxTtl = 24 * 60 * 60; - private Object deviceIdleController; + @Nullable private Method getUserIdMethod; + @Nullable + private Object deviceIdleController; + @Nullable private Method addPowerSaveTempWhitelistAppMethod; + @Nullable + @RequiresApi(Build.VERSION_CODES.S) + private Object powerExemptionManager; + @Nullable + @RequiresApi(Build.VERSION_CODES.S) + private Method addToTemporaryAllowListMethod; private class HandlerThread extends Thread { @@ -186,6 +198,7 @@ public class McsService extends Service implements Handler.Callback { } @Override + @SuppressLint("PrivateApi") public void onCreate() { super.onCreate(); TriggerReceiver.register(this); @@ -195,20 +208,27 @@ public class McsService extends Service implements Handler.Callback { powerManager = (PowerManager) getSystemService(POWER_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") == PackageManager.PERMISSION_GRANTED) { try { - String deviceIdleControllerName = "deviceidle"; - try { - Field field = Context.class.getField("DEVICE_IDLE_CONTROLLER"); - deviceIdleControllerName = (String) field.get(null); - } catch (Exception ignored) { - } - IBinder binder = (IBinder) Class.forName("android.os.ServiceManager") - .getMethod("getService", String.class).invoke(null, deviceIdleControllerName); - if (binder != null) { - deviceIdleController = Class.forName("android.os.IDeviceIdleController$Stub") - .getMethod("asInterface", IBinder.class).invoke(null, binder); - getUserIdMethod = UserHandle.class.getMethod("getUserId", int.class); - addPowerSaveTempWhitelistAppMethod = deviceIdleController.getClass() - .getMethod("addPowerSaveTempWhitelistApp", String.class, long.class, int.class, String.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + Class powerExemptionManagerClass = Class.forName("android.os.PowerExemptionManager"); + powerExemptionManager = getSystemService(powerExemptionManagerClass); + addToTemporaryAllowListMethod = + powerExemptionManagerClass.getMethod("addToTemporaryAllowList", String.class, int.class, String.class, long.class); + } else { + String deviceIdleControllerName = "deviceidle"; + try { + Field field = Context.class.getField("DEVICE_IDLE_CONTROLLER"); + deviceIdleControllerName = (String) field.get(null); + } catch (Exception ignored) { + } + IBinder binder = (IBinder) Class.forName("android.os.ServiceManager") + .getMethod("getService", String.class).invoke(null, deviceIdleControllerName); + if (binder != null) { + deviceIdleController = Class.forName("android.os.IDeviceIdleController$Stub") + .getMethod("asInterface", IBinder.class).invoke(null, binder); + getUserIdMethod = UserHandle.class.getMethod("getUserId", int.class); + addPowerSaveTempWhitelistAppMethod = deviceIdleController.getClass() + .getMethod("addPowerSaveTempWhitelistApp", String.class, long.class, int.class, String.class); + } } } catch (Exception e) { Log.w(TAG, e); @@ -594,7 +614,16 @@ public class McsService extends Service implements Handler.Callback { } private void addPowerSaveTempWhitelistApp(String packageName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + try { + if (addToTemporaryAllowListMethod != null && powerExemptionManager != null) { + logd(this, "Adding app " + packageName + " to the temp allowlist"); + addToTemporaryAllowListMethod.invoke(powerExemptionManager, packageName, 0, "GCM Push", 10000); + } + } catch (Exception e) { + Log.e(TAG, "Error adding app" + packageName + " to the temp allowlist.", e); + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { if (getUserIdMethod != null && addPowerSaveTempWhitelistAppMethod != null && deviceIdleController != null) { int userId = (int) getUserIdMethod.invoke(null, getPackageManager().getApplicationInfo(packageName, 0).uid); -- GitLab From 36d62fde7b75ebea2d238e3d8fc11721bda4ae43 Mon Sep 17 00:00:00 2001 From: Christophe Henry Date: Wed, 1 Jun 2022 14:31:55 +0200 Subject: [PATCH 13/26] Prevent an ArrayIndexOutOfBoundsException when calling extension function LongSparseArray.values() in GoogleMap --- .../src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt index 133bb7f81..eb8653a88 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt @@ -58,7 +58,7 @@ import org.microg.gms.maps.mapbox.utils.MultiArchLoader import org.microg.gms.maps.mapbox.utils.toGms import org.microg.gms.maps.mapbox.utils.toMapbox -private fun LongSparseArray.values() = (0..size()).map { valueAt(it) }.mapNotNull { it } +private fun LongSparseArray.values() = (0 until size()).mapNotNull { valueAt(it) } fun runOnMainLooper(method: () -> Unit) { if (Looper.myLooper() == Looper.getMainLooper()) { -- GitLab From 857ae8180e7ba3cd5e73523c1b1193446915fdd1 Mon Sep 17 00:00:00 2001 From: HZ is not Chatty <99131470+hustler-not-chatty@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:02:08 +0800 Subject: [PATCH 14/26] Add Simplified Chinese translations (#1727) --- .../src/main/res/values-zh-rCN/strings.xml | 7 + .../src/main/res/values-zh-rCN/strings.xml | 6 + .../src/main/res/values-zh-rCN/strings.xml | 15 ++ .../main/res/values-zh-rCN/permissions.xml | 128 ++++++++++++++ .../src/main/res/values-zh-rCN/plurals.xml | 19 +++ .../src/main/res/values-zh-rCN/strings.xml | 160 ++++++++++++++++++ .../src/main/res/values-zh-rCN/strings.xml | 8 + .../src/main/res/values-zh-rCN/strings.xml | 62 +++++++ .../src/main/res/values-zh-rCN/strings.xml | 8 + 9 files changed, 413 insertions(+) create mode 100644 play-services-base-core-ui/src/main/res/values-zh-rCN/strings.xml create mode 100644 play-services-base-core/src/main/res/values-zh-rCN/strings.xml create mode 100644 play-services-core/microg-ui-tools/src/main/res/values-zh-rCN/strings.xml create mode 100644 play-services-core/src/main/res/values-zh-rCN/permissions.xml create mode 100644 play-services-core/src/main/res/values-zh-rCN/plurals.xml create mode 100644 play-services-core/src/main/res/values-zh-rCN/strings.xml create mode 100644 play-services-droidguard-core-ui/src/main/res/values-zh-rCN/strings.xml create mode 100644 play-services-nearby-core-ui/src/main/res/values-zh-rCN/strings.xml create mode 100644 play-services-nearby-core/src/main/res/values-zh-rCN/strings.xml diff --git a/play-services-base-core-ui/src/main/res/values-zh-rCN/strings.xml b/play-services-base-core-ui/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..6919f44d3 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,7 @@ + + + 高级 + + 全部显示 + 打开 + \ No newline at end of file diff --git a/play-services-base-core/src/main/res/values-zh-rCN/strings.xml b/play-services-base-core/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..f2ce05646 --- /dev/null +++ b/play-services-base-core/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,6 @@ + + + 正在后台活动 + %1$s 正在后台运行 + %1$s 忽略电池优化,或者修改通知设置以隐藏此通知。 + \ No newline at end of file diff --git a/play-services-core/microg-ui-tools/src/main/res/values-zh-rCN/strings.xml b/play-services-core/microg-ui-tools/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..57784a43e --- /dev/null +++ b/play-services-core/microg-ui-tools/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,15 @@ + + + microG UI Tools + 版本 %1$s + %1$s %2$s + 保留所有权利。 + 设置 + 自我检查 + 确认系统是否已正确配置成 microG 可正常使用的状态 + 已授予权限 + %1$s的权限: + 点击授予权限。不授予权限可能导致应用工作异常。 + 包含的库 + 概要 + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-zh-rCN/permissions.xml b/play-services-core/src/main/res/values-zh-rCN/permissions.xml new file mode 100644 index 000000000..679bd1fca --- /dev/null +++ b/play-services-core/src/main/res/values-zh-rCN/permissions.xml @@ -0,0 +1,128 @@ + + + + 所有 Google 服务 + 允许应用通过任何已关联的 Google 账号来使用所有 Google 服务。 + + Android 服务 + 允许应用通过任何已关联的 Google 账号来使用所有 Android 服务。 + 允许应用通过任何已关联的 Google 账号来使用 AdSense。 + 允许应用通过任何已关联的 Google 账号来使用 AdWords。 + 允许应用通过任何已关联的 Google 账号来使用 Google App Engine。 + 允许应用通过任何已关联的 Google 账号来使用 Blogger。 + 允许应用通过任何已关联的 Google 账号来使用 Google 日历。 + 允许应用通过任何已关联的 Google 账号来访问联系人。 + 允许应用通过任何已关联的 Google 账号来使用 Dodgeball。 + 允许应用通过任何已关联的 Google 账号来使用 Google 财经。 + 允许应用通过任何已关联的 Google 账号来使用 Google Base。 + Google 财经 + 联系人 + Google 日历 + 允许应用通过任何已关联的 Google 账号来使用 Google Voice。 + 语音搜索 + 允许应用通过任何已关联的 Google 账号来使用语音搜索。 + 个性化语音识别 + 允许应用通过任何已关联的 Google 账号来使用个性化语音识别。 + 允许应用通过任何已关联的 Google 账号来使用 Google Talk。 + 允许应用通过任何已关联的 Google 账号来使用 Google Wi-Fi。 + 允许应用通过任何已关联的 Google 账号来使用 Google Spreadsheets。 + Google 文档 + 允许应用通过任何已关联的 Google 账号来使用 Google 文档。 + 允许应用通过任何已关联的 Google 账号来使用 YouTube。 + YouTube 用户名 + 允许应用通过任何已关联的 Google 账号获知其 YouTube 用户名。 + Picasa 网络相册 + 允许应用通过任何已关联的 Google 账号来使用 Picasa 网络相册。 + Google 地图 + 允许应用通过任何已关联的 Google 账号来使用 Google 地图。 + Google 电子邮件 + 允许应用通过任何已关联的 Google 账号来使用 Google 电子邮件。 + Google 新闻 + 允许应用通过任何已关联的 Google 账号来使用 Google 新闻。 + 允许应用通过任何已关联的 Google 账号来使用 Orkut。 + 允许应用通过任何已关联的 Google 账号来使用 Google 笔记本。 + Google 笔记本 + Google 网上论坛 + 允许应用通过任何已关联的 Google 账号来使用 Google 网上论坛。 + YouTube + Google Spreadsheets + Google Wi-Fi + Google Talk + Google 图书搜索 + 允许应用通过任何已关联的 Google 账号来使用 Google 图书搜索。 + Orkut + Dodgeball + Google Base + Google Voice + AdSense + Adwords + Google App Engine + Blogger + Google Health + 允许应用通过任何已关联的 Google 账号来使用 Google Health。 + + 管理您的 YouTube 账号 + 查看并管理您在 YouTube 上的资产和相关内容 + 查看您的 YouTube 账号 + 管理您的 YouTube 视频 + 查看您 YouTube 内容的分析报告 + 查看您 YouTube 内容的收入报告 + 查看您账号的基本内容 + 查看您的邮件地址 + 管理您的 goo.gl 短网址 + 管理您的任务 + 管理您的任务 + 查看您的任务 + Chrome 的消息推送 + 管理您的 GAN 数据 + 查看您的 GAN 数据 + 从 Google Play 游戏获取资料的权限。 + 查看您的 Freebase 账号 + 用您的账号登录到 Freebase + 管理您的图书 + 管理您的日历 + 查看您的日历 + 查看并管理您的 Google 云端打印数据 + 查看您的 Google Compute Engine 资源 + 查看并管理您的 Google Compute Engine 资源 + 查看您在 Google 应用中的活动历史 + 查看您的 AdSense 数据 + 查看并管理您的 AdSense 数据 + 查看您的 Google Analytics 数据 + 查看并管理您的 Google Analytics 数据 + 使用 Google Play Android 开发者的权限 + 查看您的 Ad Exchange 数据 + 查看并管理您的 Ad Exchange 数据 + 查看您的 Blogger 账号 + 管理您的 Blogger 账号 + 查看您在 Google Big Query 上的数据 + 查看并管理您在 Google Big Query 上的数据 + 管理您 Google 云存储中的数据 + 查看您 Google 云存储中的数据 + 管理您 Google 云存储中的数据与权限 + 查看您的 Google 云端硬盘应用 + 查看并管理此应用在您 Google 云端硬盘中打开 / 创建的文件 + 查看您 Google 云端硬盘中文件和档案的元数据 + 查看您 Google 云端硬盘中的文件和档案 + 查看并管理您 Google 云端硬盘中的文件和档案 + 管理您的 Fusion Tables + "查看您的 Fusion Tables" + 查看并管理您的 AdSense 服务资料和关联账户 + License Manager API 的读写权限。 + 知道您在 Google 是谁 + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-zh-rCN/plurals.xml b/play-services-core/src/main/res/values-zh-rCN/plurals.xml new file mode 100644 index 000000000..fccc08388 --- /dev/null +++ b/play-services-core/src/main/res/values-zh-rCN/plurals.xml @@ -0,0 +1,19 @@ + + + + 已配置 %1$d 个后端 + 已配置%1$d 个后端 + + + %1$d 个应用已注册 + %1$d 个应用已注册 + + + microG 服务核心缺少一项正常工作所需的权限。 + microG 服务核心缺少多项正常工作所需的权限。 + + + 请求缺少的一个权限 + 请求缺少的多个权限 + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-zh-rCN/strings.xml b/play-services-core/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..41db99f84 --- /dev/null +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,160 @@ + + + + 登录 + 正在建立到 Google 服务器的连接以供您登录。 + +这需要几秒钟时间。 + 您没有互联网连接。 + +这可能是暂时的问题,或者是您的 Android 设备不能使用数据服务。请在连接到移动网络或 Wi-Fi 网络后重试。 + 抱歉… + 允许 + 拒绝 + 需要认证 + 向其他应用发送 C2DM 消息 + Google 云端消息推送 + 已禁用 + 已启用 + 自动 + 手动 + + + Google Play 游戏 + 要使用 Play 游戏,您需要安装“Google Play 游戏”应用。应用在 Play 游戏未安装时可能可以继续运行,也可能发生异常。 + 选择一个地点 + 现在无法选择地点。 + 选择此地点 + 附近的地点 + microG 服务核心:缺少%1$s权限 + 移动网络 + 漫游 + 其他网络 + 签名伪装支持 + 已安装的应用 + 系统 + 系统支持签名伪装: + 您的 ROM 并不自带签名伪装支持。您仍可以利用 Xposed 或刷入支持的 ROM 来伪装签名。请查阅文档以了解哪些 ROM 自带伪装支持,以及如何在不支持的 ROM 上继续使用 microG。 + 系统已授予签名伪装权限: + 这表明您的系统极有可能支持签名伪装,但仍需要一些步骤来激活它。请查阅文档以了解后续步骤。 + Play 服务 (GmsCore) + Play 商店 (PhoneSky) + 服务框架 (GSF) + 已忽略电池优化: + 点击以忽略电池优化。保持电池优化可能导致应用行为异常。 + 关于 + 组件 + 配置 + 位置服务 + 服务 + 测试 + 电池优化正在生效 + 您在电池优化生效的情况下启用了消息推送。为保证消息正常推送,您需要对本应用忽略电池优化。 + 忽略优化 + 缺少权限 + 账号设置 + 个人资料与隐私 + 登录与安全 + 禁用后,应用在向 Google 请求验证前需要得到您的同意。有些程序可能会因此无法使用 Google 账号。 + 未注册 + 上次注册:%1$s + + microG 设置 + 设置 microG 服务 + 请稍等… + 继续则代表您同意此应用和 Google 遵循其相应的服务条款和隐私政策使用您的个人信息。 + %1$s 想要: + %1$s 想要使用: + Google 账号管理员 + "您设备上的某个应用正尝试登录一个 Google 账号。 + +若是您有意为之,点击登录以连接到 Google 的登录页面;否则点击取消以返回到弹出此页面的应用。" + "您的设备正在与 Google 联系以将您的信息存入账号。 + +这可能需要几分钟。" + "与 Google 服务器通信时发生问题。 + +请稍后重试。" + %1$s 需要您的授权以使用 Google 账号。 + 接收 C2DM 消息 + 接收内部状态广播 + 从 Google 服务器交换信息并接收同步通知。 + 向 Google 注册设备 + 允许应用在无用户交互的情况下设置 microG 服务 + %1$s 想要使用 Play 游戏 + 系统签名伪装: + 请查阅文档以了解所需步骤。 + %1$s 已安装: + 安装 %1$s 或与之兼容的应用。请查阅文档以了解有哪些兼容应用。 + %1$s 包含正确签名: + 已安装的 %1$s 不兼容,或者您未对其启用签名伪装。请查阅文档以了解兼容的应用或 ROM。 + 允许应用寻找 Google 账号 + 启用后,您设备上的所有应用将可以在不经您许可的情况下看到与您 Google 账号关联的电子邮件地址。 + 将您的设备注册到 Google 服务,并创建唯一的设备识别码。microG 将去除注册信息中您 Google 账户名以外的用于识别的信息。 + 注册设备 + 状态 + 更多 + 账号 + 添加 Google 账号 + Google 云端消息推送(GCM)是由众多第三方应用选用的消息推送提供者。使用之前您需要先向 Google 注册该设备。 + 云端消息心跳间隔 + 系统向 Google 服务器发送心跳包的间隔秒数。延长该间隔将减少电量消耗,但可能导致推送延迟。\n已废弃,将在未来版本被取代。 + 正使用云端消息推送的应用 + 已经注册使用云端消息推送的应用列表。 + 确认新应用 + 在应用注册使用消息推送之前询问您 + Ping 间隔:%1$s + 关于 microG 服务核心 + 版本信息与使用的库 + 取消注册时出错 + 已被卸载 + 取消注册 + 未注册 + 尚未收到任何消息 + 最后收到消息:%1$s + 已注册 + %1$s起注册 + 取消注册 %1$s + 有些应用并不会自动重新注册,也可能不向您提供重新注册的选项。取消注册后这些应用可能出现异常。\n是否继续? + 您拒绝了一个已经注册过的应用去注册接受推送通知。\n您是否想现在取消它的注册,使其今后不再接收消息推送? + 已接收 %1$d 条消息 (%2$d 字节) + 已断开连接 + 上次连接:%1$s + 接收推送通知 + 允许 %1$s 注册通知推送? + 允许注册 + 允许该应用注册通知推送。 + 推送时启动应用 + 适时在后台唤起应用以接收推送消息。 + 使用通知推送的应用 + 已注册的应用 + 已取消注册的应用 + 接收推送时使用的网络 + 允许设备认证 + 测试 ReCAPTCHA + 所有测试已通过 + 失败:%s + 警告:%s + 正在运行… + 运作模式 + 本地 + 真实 + 自定义:%s + 自动:%s + 系统:%s + "测试 SafetyNet 认证 " + Google SafetyNet 是一套设备认证系统,旨在确认设备被切实保护,并与 Android CTS 兼容。某些应用会出于安全考虑或是防篡改目的而使用 SafetyNet。 + +microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要求 SafetyNet 请求被其专有的 DroidGuard 系统签名。 + Google 服务 + 额外使用 Google 服务 + + + + Google + Wi-Fi + (%1$.7f, %2$.7f) + 预配置 microG 服务 + microG 服务核心 + 允许 Google 向应用提供验证 + \ No newline at end of file diff --git a/play-services-droidguard-core-ui/src/main/res/values-zh-rCN/strings.xml b/play-services-droidguard-core-ui/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..bbbbc5308 --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,8 @@ + + + DroidGuard 运作模式 + 通过网络连接到 DroidGuard 运行时 + 内置 + 远程 + 使用本地 DroidGuard 运行时 + \ No newline at end of file diff --git a/play-services-nearby-core-ui/src/main/res/values-zh-rCN/strings.xml b/play-services-nearby-core-ui/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..3ca57c23d --- /dev/null +++ b/play-services-nearby-core-ui/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,62 @@ + + + 接触史通知系统 + 要启用接触史通知系统,请打开支持该系统的应用。 + 启用蓝牙 + 打开位置设置 + 很抱歉,您的设备与接触史通知系统不兼容。 + 很抱歉,您的设备与接触史通知系统仅部分兼容。您可以收到接触风险通知,但无法向他人发出通知。 + 使用接触史通知系统的应用 + 已收集的标识符 + 一小时内已收集 %1$d 个标识符 + 当前已广播的标识符 + 已报告接触史 + 最后更新于:%1$s + 不到 5 分钟 + %1$d 分钟 + 没有已报告的接触者。 + 注意:风险评分由应用定义。数字更高可能代表低风险,或反之。 + 14 天内的 API 使用情况 + 无记录 + 删除 + 删除所有已收集的标识符 + 删除所有已收集的标识符会导致您无法在最近 14 天内接触过确诊对象时获得通知。 + 仍要删除 + 导出 + 启用 + 需要新权限 + 更新设置 + 关闭 + 打开 + 打开接触史通知? + 使用接触史通知 + "接触史通知启用时,您的设备被动地从附近的设备收集滚动邻近标识符(RPI)。 + +其他设备拥有者被确诊为阳性时,他们的标识将被共享。您的设备将检查是否曾收集到已知确诊患者的标识,并据此计算您的感染风险。" + "接触史通知 API 允许应用在您曾经与新冠肺炎阳性确诊患者接触时通知您。 + +与接触相关的日期、时长和信号强度将被共享给相应的应用。 + 导出已收集的标识符以供其他应用进一步分析。 + 关闭接触史通知? + 近距离暴露 + 远距离暴露 + %1$s 需要额外权限。 + 授予权限 + 需要打开蓝牙。 + 需要位置访问权限。 + 马上就好!请在下一屏幕中选择“一律允许”以授予后台位置访问权限,然后返回。 + %1$s 共享您的标识符? + 共享 + 关闭暴露史通知后,您将无法在最近接触过确诊对象时获得通知。 + 点击以向暴露史通知授予权限。 + 您最近 14 天的标识符将被用来帮助向您附近的人通知您的潜在接触史。不会向他人共享您的个人身份和检测结果。 + "您的设备需要蓝牙以安全地收集并与附近的其他设备共享标识符。 + +%1$s 可以在您曾与新冠肺炎阳性确诊患者接触时通知您。 + +接触时的日期、时长和与信号强度将被共享给相应的应用。" + 每小时 %1$s 个标识符 + 已收集 %1$d 个标识符 + %1$s,风险评分:%2$d + %1$s, %2$s + \ No newline at end of file diff --git a/play-services-nearby-core/src/main/res/values-zh-rCN/strings.xml b/play-services-nearby-core/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..c683b93fc --- /dev/null +++ b/play-services-nearby-core/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,8 @@ + + + 接触史通知未启用 + 需要启用蓝牙以接收接触史通知。 + 需要位置访问权限以接收接触史通知。 + 需要启用蓝牙和位置访问权限以接收接触史通知。 + 接触史通知需要额外权限来运作 + \ No newline at end of file -- GitLab From 758fb760834fb01c10a797df1b4a6cd88dd9120f Mon Sep 17 00:00:00 2001 From: Matthieu Baerts Date: Fri, 4 Feb 2022 17:52:43 +0100 Subject: [PATCH 15/26] MCS: fallback to port 443 if 5228 is blocked According to Google Firebase's doc, other ports than 5228 can be used: If your organization has a firewall to restrict traffic to or from the Internet, you need to configure it to allow mobile devices to connect with FCM in order for devices on your network to receive messages. FCM typically uses port 5228, but it sometimes uses 443, 5229, and 5230. Src: https://firebase.google.com/docs/cloud-messaging/concept-options#messaging-ports-and-your-firewall As suggested by @mar-v-in, it is enough to first try 5228 and then only try with 443 as fall back port. Indeed, it would be surprising to find any firewall blocking port 5228 but not 5229. If none of these ports, a new attempt will be done later as before. Closes: https://github.com/microg/GmsCore/issues/408 Signed-off-by: Matthieu Baerts --- .../java/org/microg/gms/gcm/McsService.java | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 8dad5fc9e..2142648b1 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -123,7 +123,10 @@ public class McsService extends Service implements Handler.Callback { public static final String FROM_FIELD = "gcm@android.com"; public static final String SERVICE_HOST = "mtalk.google.com"; - public static final int SERVICE_PORT = 5228; + // A few ports are available: 443, 5228-5230 but also 5222-5223 + // See https://github.com/microg/GmsCore/issues/408 + // Likely if the main port 5228 is blocked by a firewall, the other 52xx are blocked as well + public static final int[] SERVICE_PORTS = {5228, 443}; private static final int WAKELOCK_TIMEOUT = 5000; // On bad mobile network a ping can take >60s, so we wait for an ACK for 90s @@ -460,38 +463,52 @@ public class McsService extends Service implements Handler.Callback { } } + private void connect(int port) throws Exception { + this.wasTornDown = false; + + logd(this, "Starting MCS connection to port " + port + "..."); + Socket socket = new Socket(SERVICE_HOST, port); + logd(this, "Connected to " + SERVICE_HOST + ":" + port); + sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, port, true); + logd(this, "Activated SSL with " + SERVICE_HOST + ":" + port); + inputStream = new McsInputStream(sslSocket.getInputStream(), rootHandler); + outputStream = new McsOutputStream(sslSocket.getOutputStream(), rootHandler); + inputStream.start(); + outputStream.start(); + + startTimestamp = System.currentTimeMillis(); + lastHeartbeatPingElapsedRealtime = SystemClock.elapsedRealtime(); + lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); + lastIncomingNetworkRealtime = SystemClock.elapsedRealtime(); + scheduleHeartbeat(this); + } + private synchronized void connect() { - try { - closeAll(); - ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); - activeNetworkPref = GcmPrefs.get(this).getNetworkPrefForInfo(activeNetworkInfo); - if (!GcmPrefs.get(this).isEnabledFor(activeNetworkInfo)) { - logd(this, "Don't connect, because disabled for " + activeNetworkInfo.getTypeName()); - scheduleReconnect(this); + closeAll(); + + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); + activeNetworkPref = GcmPrefs.get(this).getNetworkPrefForInfo(activeNetworkInfo); + if (!GcmPrefs.get(this).isEnabledFor(activeNetworkInfo)) { + logd(this, "Don't connect, because disabled for " + activeNetworkInfo.getTypeName()); + scheduleReconnect(this); + return; + } + + Exception exception = null; + for (int port : SERVICE_PORTS) { + try { + connect(port); return; + } catch (Exception e) { + exception = e; + Log.w(TAG, "Exception while connecting to " + SERVICE_HOST + ":" + port, e); + closeAll(); } - wasTornDown = false; - - logd(this, "Starting MCS connection..."); - Socket socket = new Socket(SERVICE_HOST, SERVICE_PORT); - logd(this, "Connected to " + SERVICE_HOST + ":" + SERVICE_PORT); - sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, SERVICE_PORT, true); - logd(this, "Activated SSL with " + SERVICE_HOST + ":" + SERVICE_PORT); - inputStream = new McsInputStream(sslSocket.getInputStream(), rootHandler); - outputStream = new McsOutputStream(sslSocket.getOutputStream(), rootHandler); - inputStream.start(); - outputStream.start(); - - startTimestamp = System.currentTimeMillis(); - lastHeartbeatPingElapsedRealtime = SystemClock.elapsedRealtime(); - lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); - lastIncomingNetworkRealtime = SystemClock.elapsedRealtime(); - scheduleHeartbeat(this); - } catch (Exception e) { - Log.w(TAG, "Exception while connecting!", e); - rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e)); } + + logd(this, "Unable to connect to all different ports, retrying later"); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, exception)); } private void handleClose(Close close) { -- GitLab From a841305d0afdac065b348d6424e38a93780af768 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 7 Jul 2022 09:57:41 +0200 Subject: [PATCH 16/26] Update Location APIs --- .../gms/location/reporting/OptInRequest.aidl | 3 ++ .../location/reporting/SendDataRequest.aidl | 3 ++ .../reporting/UlrPrivateModeRequest.aidl | 3 ++ .../reporting/internal/IReportingService.aidl | 8 +++- .../location/DeviceOrientationRequest.java | 2 +- .../android/gms/location/LocationRequest.java | 2 +- .../gms/location/reporting/OptInRequest.java | 22 +++++++++++ .../location/reporting/ReportingState.java | 23 ++++++----- .../location/reporting/SendDataRequest.java | 19 ++++++++++ .../reporting/UlrPrivateModeRequest.java | 17 +++++++++ .../gms/location/GoogleLocationManager.java | 2 +- .../GoogleLocationManagerService.java | 2 +- .../gms/location/LocationRequestHelper.java | 11 +++++- .../gms/location/RealLocationProvider.java | 2 +- .../gms/location/ReportingAndroidService.java | 2 +- .../gms/location/ReportingServiceImpl.java | 38 +++++++++++++++++-- .../gms/location/UnifiedLocationProvider.kt | 2 +- 17 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/OptInRequest.aidl create mode 100644 play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/SendDataRequest.aidl create mode 100644 play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/UlrPrivateModeRequest.aidl create mode 100644 play-services-location-api/src/main/java/com/google/android/gms/location/reporting/OptInRequest.java create mode 100644 play-services-location-api/src/main/java/com/google/android/gms/location/reporting/SendDataRequest.java create mode 100644 play-services-location-api/src/main/java/com/google/android/gms/location/reporting/UlrPrivateModeRequest.java diff --git a/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/OptInRequest.aidl b/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/OptInRequest.aidl new file mode 100644 index 000000000..291d45308 --- /dev/null +++ b/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/OptInRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.location.reporting; + +parcelable OptInRequest; diff --git a/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/SendDataRequest.aidl b/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/SendDataRequest.aidl new file mode 100644 index 000000000..f353a806b --- /dev/null +++ b/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/SendDataRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.location.reporting; + +parcelable SendDataRequest; diff --git a/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/UlrPrivateModeRequest.aidl b/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/UlrPrivateModeRequest.aidl new file mode 100644 index 000000000..2a4eb27ce --- /dev/null +++ b/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/UlrPrivateModeRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.location.reporting; + +parcelable UlrPrivateModeRequest; diff --git a/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/internal/IReportingService.aidl b/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/internal/IReportingService.aidl index 715b6779d..8f7d1d607 100644 --- a/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/internal/IReportingService.aidl +++ b/play-services-location-api/src/main/aidl/com/google/android/gms/location/reporting/internal/IReportingService.aidl @@ -2,14 +2,20 @@ package com.google.android.gms.location.reporting.internal; import android.accounts.Account; import com.google.android.gms.location.places.PlaceReport; +import com.google.android.gms.location.reporting.OptInRequest; import com.google.android.gms.location.reporting.ReportingState; +import com.google.android.gms.location.reporting.SendDataRequest; +import com.google.android.gms.location.reporting.UlrPrivateModeRequest; import com.google.android.gms.location.reporting.UploadRequest; import com.google.android.gms.location.reporting.UploadRequestResult; interface IReportingService { ReportingState getReportingState(in Account account) = 0; - int tryOptIn(in Account account) = 1; + int tryOptInAccount(in Account account) = 1; UploadRequestResult requestUpload(in UploadRequest request) = 2; int cancelUploadRequest(long l) = 3; int reportDeviceAtPlace(in Account account, in PlaceReport report) = 4; + int tryOptIn(in OptInRequest request) = 5; + int sendData(in SendDataRequest request) = 6; + int requestPrivateMode(in UlrPrivateModeRequest request) = 7; } diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientationRequest.java b/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientationRequest.java index 14ce93c1f..8326c457e 100644 --- a/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientationRequest.java +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientationRequest.java @@ -28,7 +28,7 @@ public class DeviceOrientationRequest extends AutoSafeParcelable { sb.append(" minimumSamplingPeriod=").append(minimumSamplingPeriodMs).append("ms"); sb.append(" smallesAngleChange=").append(smallesAngleChangeRadians).append("rad"); if (expirationTime != Long.MAX_VALUE) - sb.append(" expireIn=").append(SystemClock.elapsedRealtime() - expirationTime).append("ms"); + sb.append(" expireIn=").append(expirationTime - SystemClock.elapsedRealtime()).append("ms"); if (numUpdates != Integer.MAX_VALUE) sb.append(" num=").append(numUpdates); sb.append("]"); diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/LocationRequest.java b/play-services-location-api/src/main/java/com/google/android/gms/location/LocationRequest.java index 085886c0a..c418116eb 100644 --- a/play-services-location-api/src/main/java/com/google/android/gms/location/LocationRequest.java +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/LocationRequest.java @@ -516,7 +516,7 @@ public class LocationRequest extends AutoSafeParcelable { if (smallestDisplacement > 0) sb.append(" smallestDisplacement=").append(smallestDisplacement).append("m"); if (expirationTime != Long.MAX_VALUE) - sb.append(" expireIn=").append(SystemClock.elapsedRealtime() - expirationTime).append("ms"); + sb.append(" expireIn=").append(expirationTime - SystemClock.elapsedRealtime()).append("ms"); if (numUpdates != Integer.MAX_VALUE) sb.append(" num=").append(numUpdates); sb.append("]"); diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/OptInRequest.java b/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/OptInRequest.java new file mode 100644 index 000000000..5ade88013 --- /dev/null +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/OptInRequest.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.location.reporting; + +import android.accounts.Account; + +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; + +public class OptInRequest extends AutoSafeParcelable { + @Field(2) + public Account account; + @Field(3) + public String tag; + @Field(4) + public String auditToken; + + public static final Creator CREATOR = new AutoCreator(OptInRequest.class); +} diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/ReportingState.java b/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/ReportingState.java index 1a2234716..98a34d05b 100644 --- a/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/ReportingState.java +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/ReportingState.java @@ -20,24 +20,27 @@ import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; public class ReportingState extends AutoSafeParcelable { - @SafeParceled(1) - public int versionCode = 2; - @SafeParceled(2) + @Field(1) + @Deprecated + private int versionCode = 2; + @Field(2) public int reportingEnabled; - @SafeParceled(3) + @Field(3) public int historyEnabled; - @SafeParceled(4) + @Field(4) public boolean allowed; - @SafeParceled(5) + @Field(5) public boolean active; - @SafeParceled(6) + @Field(6) public boolean defer; - @SafeParceled(7) + @Field(7) public int expectedOptInResult; - @SafeParceled(8) + @Field(8) public Integer deviceTag; - @SafeParceled(9) + @Field(9) public int expectedOptInResultAssumingLocationEnabled; + @Field(10) + public boolean canAccessSettings; public static final Creator CREATOR = new AutoCreator(ReportingState.class); } diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/SendDataRequest.java b/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/SendDataRequest.java new file mode 100644 index 000000000..f440fa8d1 --- /dev/null +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/SendDataRequest.java @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.location.reporting; + +import android.accounts.Account; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class SendDataRequest extends AutoSafeParcelable { + @Field(1) + public String dataType; + @Field(2) + public byte[] data; + + public static final Creator CREATOR = new AutoCreator(SendDataRequest.class); +} diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/UlrPrivateModeRequest.java b/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/UlrPrivateModeRequest.java new file mode 100644 index 000000000..cfa348ca5 --- /dev/null +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/reporting/UlrPrivateModeRequest.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.location.reporting; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class UlrPrivateModeRequest extends AutoSafeParcelable { + @Field(1) + public String tag; + @Field(2) + public boolean privateMode; + + public static final Creator CREATOR = new AutoCreator(UlrPrivateModeRequest.class); +} diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java index f8c5d04e2..2e3f68f18 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java @@ -287,7 +287,7 @@ public class GoogleLocationManager implements LocationChangeListener { if (networkProvider != null) networkProvider.dump(writer); writer.println(currentRequests.size() + " requests:"); for (LocationRequestHelper request : currentRequests) { - writer.println(" " + request.id + " package=" + request.packageName + " interval=" + request.locationRequest.getInterval() + " smallestDisplacement=" + request.locationRequest.getSmallestDisplacement()); + request.dump(writer); } } } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java index bf6c98701..2565a8091 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java @@ -31,7 +31,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; public class GoogleLocationManagerService extends BaseService { - private GoogleLocationManagerServiceImpl impl = new GoogleLocationManagerServiceImpl(this, getLifecycle()); + private final GoogleLocationManagerServiceImpl impl = new GoogleLocationManagerServiceImpl(this, getLifecycle()); public GoogleLocationManagerService() { super("LocationManager", GmsService.LOCATION_MANAGER, GmsService.GEODATA, GmsService.PLACE_DETECTION); diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java b/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java index 6fdae8d63..9ea293f51 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java @@ -26,6 +26,7 @@ import android.location.Location; import android.os.Binder; import android.os.Build; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Log; import com.google.android.gms.location.ILocationCallback; @@ -34,6 +35,7 @@ import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.internal.LocationRequestUpdateData; +import java.io.PrintWriter; import java.util.Arrays; import java.util.UUID; @@ -88,6 +90,7 @@ public class LocationRequestHelper { public boolean isActive() { if (!hasCoarsePermission()) return false; + if (locationRequest.getExpirationTime() < SystemClock.elapsedRealtime()) return false; if (listener != null) { try { return listener.asBinder().isBinderAlive(); @@ -111,8 +114,8 @@ public class LocationRequestHelper { * @return whether to continue sending reports to this {@link LocationRequestHelper} */ public boolean report(Location location) { - if (location == null) return true; if (!isActive()) return false; + if (location == null) return true; if (lastReport != null) { if (location.equals(lastReport)) { return true; @@ -239,4 +242,10 @@ public class LocationRequestHelper { result = 31 * result + (callback != null ? callback.hashCode() : 0); return result; } + + public void dump(PrintWriter writer) { + writer.println(" " + id + " package=" + packageName); + writer.println(" request: " + locationRequest); + writer.println(" last location: " + lastReport); + } } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java b/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java index cb4fd496d..c9111b9a6 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java @@ -31,7 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @SuppressWarnings("MissingPermission") public class RealLocationProvider { public static final String TAG = "GmsLocProviderReal"; - private static final int MIN_GPS_TIME = 10000; + private static final int MIN_GPS_TIME = 60000; private final LocationManager locationManager; private final String name; diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/ReportingAndroidService.java b/play-services-location-core/src/main/java/org/microg/gms/location/ReportingAndroidService.java index d382cdccf..6ab500b72 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/ReportingAndroidService.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/ReportingAndroidService.java @@ -25,7 +25,7 @@ import org.microg.gms.BaseService; import org.microg.gms.common.GmsService; public class ReportingAndroidService extends BaseService { - private ReportingServiceImpl reportingService = new ReportingServiceImpl(); + private ReportingServiceImpl reportingService = new ReportingServiceImpl(this); public ReportingAndroidService() { super("GmsLocReportingSvc", GmsService.LOCATION_REPORTING); diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/ReportingServiceImpl.java b/play-services-location-core/src/main/java/org/microg/gms/location/ReportingServiceImpl.java index f369f84ed..705f7d4a2 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/ReportingServiceImpl.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/ReportingServiceImpl.java @@ -17,29 +17,45 @@ package org.microg.gms.location; import android.accounts.Account; +import android.content.Context; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; import com.google.android.gms.location.places.PlaceReport; +import com.google.android.gms.location.reporting.OptInRequest; import com.google.android.gms.location.reporting.ReportingState; +import com.google.android.gms.location.reporting.SendDataRequest; +import com.google.android.gms.location.reporting.UlrPrivateModeRequest; import com.google.android.gms.location.reporting.UploadRequest; import com.google.android.gms.location.reporting.UploadRequestResult; import com.google.android.gms.location.reporting.internal.IReportingService; +import org.microg.gms.common.PackageUtils; + public class ReportingServiceImpl extends IReportingService.Stub { private static final String TAG = "GmsLocReportSvcImpl"; + private Context context; + + public ReportingServiceImpl(Context context) { + this.context = context; + } @Override public ReportingState getReportingState(Account account) throws RemoteException { Log.d(TAG, "getReportingState"); - return new ReportingState(); + ReportingState state = new ReportingState(); + if (PackageUtils.callerHasExtendedAccess(context)) { + state.deviceTag = 0; + } + return state; } @Override - public int tryOptIn(Account account) throws RemoteException { - Log.d(TAG, "tryOptIn"); - return 0; + public int tryOptInAccount(Account account) throws RemoteException { + OptInRequest request = new OptInRequest(); + request.account = account; + return tryOptIn(request); } @Override @@ -60,6 +76,20 @@ public class ReportingServiceImpl extends IReportingService.Stub { return 0; } + @Override + public int tryOptIn(OptInRequest request) throws RemoteException { + return 0; + } + + @Override + public int sendData(SendDataRequest request) throws RemoteException { + return 0; + } + + @Override + public int requestPrivateMode(UlrPrivateModeRequest request) throws RemoteException { + return 0; + } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt b/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt index f4df00fa7..2edda75cd 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt +++ b/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt @@ -129,7 +129,7 @@ class UnifiedLocationProvider(private val context: Context, private val changeLi fun dump(writer: PrintWriter) { writer.println("network provider (via direct client):") - writer.println(" last location: $lastLocation") + writer.println(" last location: ${lastLocation?.let { Location(it) }}") writer.println(" ready: $ready") } -- GitLab From 4dc57966ab587e0be09def932ec725c09f798808 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 7 Jul 2022 10:05:13 +0200 Subject: [PATCH 17/26] Nearby: Show weekday for reported exposures Fixes #1743 --- .../core/ui/ExposureNotificationsAppPreferencesFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppPreferencesFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppPreferencesFragment.kt index f6883c224..7e9feb3f5 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppPreferencesFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppPreferencesFragment.kt @@ -111,7 +111,7 @@ class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() { } } preference.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_alert) - preference.title = DateUtils.formatDateRange(requireContext(), exposure.timestamp, exposure.timestamp + exposure.durationInMinutes * 60 * 1000L, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE) + preference.title = DateUtils.formatDateRange(requireContext(), exposure.timestamp, exposure.timestamp + exposure.durationInMinutes * 60 * 1000L, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_ABBREV_WEEKDAY) preference.summary = getString(R.string.pref_exposure_app_report_entry_combined, durationString, distanceString) preference.isSelectable = false reportedExposures.addPreference(preference) -- GitLab From 8976c8b226cd74a0117dcb036cad29236f2ceade Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 7 Jul 2022 10:06:18 +0200 Subject: [PATCH 18/26] Nearby: Add NOT_IN_ALLOWLIST and deprecate NOT_IN_WHITELIST --- .../exposurenotification/ExposureNotificationStatus.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationStatus.java b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationStatus.java index feabbaf93..660d0c391 100644 --- a/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationStatus.java +++ b/play-services-nearby-api/src/main/java/com/google/android/gms/nearby/exposurenotification/ExposureNotificationStatus.java @@ -41,7 +41,7 @@ public enum ExposureNotificationStatus { /** * The client is not in approved client list. */ - NOT_IN_WHITELIST, + NOT_IN_ALLOWLIST, /** * Can't detected the BLE supporting of this device due to bluetooth is not enabled. */ @@ -69,10 +69,13 @@ public enum ExposureNotificationStatus { /** * Exposure notification is not supported for current user profile. */ - USER_PROFILE_NOT_SUPPORT + USER_PROFILE_NOT_SUPPORT, + @Deprecated + NOT_IN_WHITELIST ; private long flag() { + if (this == NOT_IN_WHITELIST) return NOT_IN_ALLOWLIST.flag(); return 1 << ordinal(); } @@ -89,8 +92,10 @@ public enum ExposureNotificationStatus { public static Set flagsToSet(long flags) { Set set = new HashSet<>(); for (ExposureNotificationStatus status : values()) { + if (status == NOT_IN_WHITELIST) continue; if ((flags & status.flag()) > 0) { set.add(status); + if (status == NOT_IN_ALLOWLIST) set.add(NOT_IN_WHITELIST); } } return set; -- GitLab From c2fdf4611e7b40f9166284abcd75da3a7cc4a6d6 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 7 Jul 2022 10:08:08 +0200 Subject: [PATCH 19/26] Auth: Update API and fixes --- .../android/auth/IAuthManagerService.aidl | 6 +++++ .../gms/auth/GetHubTokenInternalResponse.aidl | 3 +++ .../android/gms/auth/GetHubTokenRequest.aidl | 3 +++ .../gms/auth/HasCababilitiesRequest.aidl | 3 +++ .../gms/auth/AccountChangeEventsRequest.java | 23 +++++++++++++++---- .../gms/auth/GetHubTokenInternalResponse.java | 21 +++++++++++++++++ .../android/gms/auth/GetHubTokenRequest.java | 20 ++++++++++++++++ .../gms/auth/HasCababilitiesRequest.java | 18 +++++++++++++++ .../org/microg/gms/common/HttpFormClient.java | 5 ++++ .../gms/auth/AccountContentProvider.java | 16 +++++++++++-- .../gms/auth/AuthManagerServiceImpl.java | 16 +++++++++++++ .../loginservice/AccountAuthenticator.java | 4 +++- .../gms/ui/AccountSettingsActivity.java | 7 +++--- 13 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenInternalResponse.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenRequest.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl create mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java diff --git a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl index 3bc93136a..ae33a3349 100644 --- a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl @@ -5,13 +5,19 @@ import android.accounts.Account; import com.google.android.gms.auth.AccountChangeEventsResponse; import com.google.android.gms.auth.AccountChangeEventsRequest; +import com.google.android.gms.auth.GetHubTokenRequest; +import com.google.android.gms.auth.GetHubTokenInternalResponse; +import com.google.android.gms.auth.HasCababilitiesRequest; interface IAuthManagerService { Bundle getToken(String accountName, String scope, in Bundle extras) = 0; Bundle clearToken(String token, in Bundle extras) = 1; AccountChangeEventsResponse getChangeEvents(in AccountChangeEventsRequest request) = 2; + Bundle getTokenWithAccount(in Account account, String scope, in Bundle extras) = 4; Bundle getAccounts(in Bundle extras) = 5; Bundle removeAccount(in Account account) = 6; Bundle requestGoogleAccountsAccess(String packageName) = 7; + int hasCapabilities(in HasCababilitiesRequest request) = 8; + GetHubTokenInternalResponse getHubToken(in GetHubTokenRequest request, in Bundle extras) = 9; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenInternalResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenInternalResponse.aidl new file mode 100644 index 000000000..f73f91bb7 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenInternalResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth; + +parcelable GetHubTokenInternalResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenRequest.aidl new file mode 100644 index 000000000..74de27766 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth; + +parcelable GetHubTokenRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl new file mode 100644 index 000000000..c0c080cd1 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth; + +parcelable HasCababilitiesRequest; diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java index 0d7f5cd8f..f746a6439 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java @@ -16,16 +16,29 @@ package com.google.android.gms.auth; +import static org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE; + +import android.accounts.Account; + import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; public class AccountChangeEventsRequest extends AutoSafeParcelable { - @SafeParceled(1) + @Field(1) private int versionCode = 1; - @SafeParceled(2) - private int i; - @SafeParceled(3) - private String s; + @Field(2) + private int since; + @Field(3) + @Deprecated + private String accountName; + @Field(4) + private Account account; + + public Account getAccount() { + if (account != null) return account; + if (accountName != null) return new Account(accountName, DEFAULT_ACCOUNT_TYPE); + return null; + } public static Creator CREATOR = new AutoCreator(AccountChangeEventsRequest.class); diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java new file mode 100644 index 000000000..ccecf2914 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth; + +import android.accounts.Account; +import android.content.Intent; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetHubTokenInternalResponse extends AutoSafeParcelable { + @Field(1) + public TokenData tokenData; + @Field(2) + public String status; + @Field(3) + public Intent recoveryIntent; + public static final Creator CREATOR = new AutoCreator<>(GetHubTokenInternalResponse.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java new file mode 100644 index 000000000..cdaffb519 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetHubTokenRequest extends AutoSafeParcelable { + @Field(1) + public String accountName; + @Field(2) + public String service; + @Field(3) + public String packageName; + @Field(4) + public int callerUid; + public static final Creator CREATOR = new AutoCreator<>(GetHubTokenRequest.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java new file mode 100644 index 000000000..27b94732c --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth; + +import android.accounts.Account; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class HasCababilitiesRequest extends AutoSafeParcelable { + @Field(1) + public Account account; + @Field(2) + public String[] capabilities; + public static final Creator CREATOR = new AutoCreator<>(HasCababilitiesRequest.class); +} diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/HttpFormClient.java b/play-services-base-core/src/main/java/org/microg/gms/common/HttpFormClient.java index 8aee1df75..a00554e48 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/HttpFormClient.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/HttpFormClient.java @@ -133,10 +133,12 @@ public class HttpFormClient { String[] keyValuePair = s.split("=", 2); String key = keyValuePair[0].trim(); String value = keyValuePair[1].trim(); + boolean matched = false; try { for (Field field : tClass.getDeclaredFields()) { if (field.isAnnotationPresent(ResponseField.class) && key.equals(field.getAnnotation(ResponseField.class).value())) { + matched = true; if (field.getType().equals(String.class)) { field.set(response, value); } else if (field.getType().equals(boolean.class)) { @@ -151,6 +153,9 @@ public class HttpFormClient { } catch (Exception e) { Log.w(TAG, e); } + if (!matched) { + Log.w(TAG, "Response line '" + s + "' not processed"); + } } for (Field field : tClass.getDeclaredFields()) { if (field.isAnnotationPresent(ResponseHeader.class)) { diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java b/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java index b1fb48222..bba39ab53 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java @@ -16,6 +16,8 @@ package org.microg.gms.auth; +import static android.accounts.AccountManager.VISIBILITY_VISIBLE; + import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; @@ -57,7 +59,7 @@ public class AccountContentProvider extends ContentProvider { suggestedPackageName = getCallingPackage(); } String packageName = PackageUtils.getAndCheckCallingPackage(getContext(), suggestedPackageName); - Log.d(TAG, "Call from " + packageName); + Log.d(TAG, "Call " + method + " from " + packageName + " with arg " + arg); if (!PackageUtils.callerHasExtendedAccess(getContext())) { String[] packagesForUid = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid()); if (packagesForUid != null && packagesForUid.length != 0) @@ -71,17 +73,27 @@ public class AccountContentProvider extends ContentProvider { Account[] accounts = null; if (arg != null && (arg.equals(DEFAULT_ACCOUNT_TYPE) || arg.startsWith(DEFAULT_ACCOUNT_TYPE + "."))) { AccountManager am = AccountManager.get(getContext()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (Build.VERSION.SDK_INT >= 18) { accounts = am.getAccountsByTypeForPackage(arg, packageName); } if (accounts == null || accounts.length == 0) { accounts = am.getAccountsByType(arg); } + if (Build.VERSION.SDK_INT >= 26 && accounts != null && arg.equals(DEFAULT_ACCOUNT_TYPE)) { + for (Account account : accounts) { + if (am.getAccountVisibility(account, packageName) == AccountManager.VISIBILITY_UNDEFINED) { + Log.d(TAG, "Make account " + account + " visible to " + packageName); + am.setAccountVisibility(account, packageName, VISIBILITY_VISIBLE); + } + } + } } if (accounts == null) { accounts = new Account[0]; } + result.putParcelableArray(PROVIDER_EXTRA_ACCOUNTS, accounts); + Log.d(TAG, "get_accounts returns: " + Arrays.toString(accounts)); return result; } else if (PROVIDER_METHOD_CLEAR_PASSWORD.equals(method)) { Account a = extras.getParcelable(PROVIDER_EXTRA_CLEAR_PASSWORD); diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index d2f5b6628..59422af29 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -38,6 +38,9 @@ import com.google.android.auth.IAuthManagerService; import com.google.android.gms.R; import com.google.android.gms.auth.AccountChangeEventsRequest; import com.google.android.gms.auth.AccountChangeEventsResponse; +import com.google.android.gms.auth.GetHubTokenInternalResponse; +import com.google.android.gms.auth.GetHubTokenRequest; +import com.google.android.gms.auth.HasCababilitiesRequest; import com.google.android.gms.auth.TokenData; import com.google.android.gms.common.api.Scope; @@ -45,6 +48,7 @@ import org.microg.gms.common.PackageUtils; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -205,6 +209,18 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { return null; } + @Override + public int hasCapabilities(HasCababilitiesRequest request) throws RemoteException { + Log.w(TAG, "Not implemented: hasCapabilities(" + request.account + ", " + Arrays.toString(request.capabilities) + ")"); + return 1; + } + + @Override + public GetHubTokenInternalResponse getHubToken(GetHubTokenRequest request, Bundle extras) throws RemoteException { + Log.w(TAG, "Not implemented: getHubToken()"); + return null; + } + @Override @SuppressLint("MissingPermission") // Workaround bug in Android Linter public Bundle clearToken(String token, Bundle extras) { diff --git a/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java b/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java index c52565ce0..5b4118480 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java @@ -147,8 +147,10 @@ class AccountAuthenticator extends AbstractAccountAuthenticator { if (services != null) { List servicesList = Arrays.asList(services.split(",")); for (String feature : features) { - if (feature.startsWith("service_") && !servicesList.contains(feature.substring(8))) + if (feature.startsWith("service_") && !servicesList.contains(feature.substring(8))) { + Log.d(TAG, "Feature " + feature + " not supported"); res = false; + } } } else { res = false; diff --git a/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java index 9fa7c11db..491b72010 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java @@ -28,13 +28,12 @@ import androidx.preference.Preference; import com.google.android.gms.R; import org.microg.gms.auth.AuthConstants; -import org.microg.gms.auth.AuthManager; import org.microg.tools.ui.AbstractSettingsActivity; import org.microg.tools.ui.ResourceSettingsFragment; import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE; -import static android.accounts.AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE; -import static android.accounts.AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; +import static android.accounts.AccountManager.VISIBILITY_NOT_VISIBLE; +import static android.accounts.AccountManager.VISIBILITY_VISIBLE; import static org.microg.gms.auth.AuthManager.PREF_AUTH_VISIBLE; public class AccountSettingsActivity extends AbstractSettingsActivity { @@ -61,7 +60,7 @@ public class AccountSettingsActivity extends AbstractSettingsActivity { if (newValue instanceof Boolean) { AccountManager am = AccountManager.get(getContext()); for (Account account : am.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)) { - am.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, (Boolean) newValue ? VISIBILITY_USER_MANAGED_VISIBLE : VISIBILITY_USER_MANAGED_NOT_VISIBLE); + am.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, (Boolean) newValue ? VISIBILITY_VISIBLE : VISIBILITY_NOT_VISIBLE); } } return true; -- GitLab From b042407e666a3d657c85160c848c4f91f1bc5ece Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 7 Jul 2022 10:08:51 +0200 Subject: [PATCH 20/26] SafetyNet: Update API --- .../org/microg/gms/ui/PushNotificationFragment.kt | 9 ++++----- .../kotlin/org/microg/gms/ui/SafetyNetFragment.kt | 9 ++++----- .../gms/safetynet/internal/ISafetyNetService.aidl | 14 +++++++++++++- .../gms/safetynet/SafetyNetStatusCodes.java | 2 +- .../microg/gms/safetynet/SafetyNetClientService.kt | 10 ++++++++-- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt index 951276894..5f8dc3743 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt @@ -19,6 +19,10 @@ import org.microg.gms.gcm.setGcmServiceConfiguration class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { lateinit var binding: PushNotificationFragmentBinding + init { + setHasOptionsMenu(true) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = PushNotificationFragmentBinding.inflate(inflater, container, false) binding.switchBarCallback = object : PreferenceSwitchBarCallback { @@ -52,11 +56,6 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - setHasOptionsMenu(true) - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced) super.onCreateOptionsMenu(menu, inflater) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt index 88656d7c3..e3cca8cc7 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt @@ -20,6 +20,10 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { private lateinit var binding: SafetyNetFragmentBinding + init { + setHasOptionsMenu(true) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = SafetyNetFragmentBinding.inflate(inflater, container, false) binding.switchBarCallback = object : PreferenceSwitchBarCallback { @@ -52,11 +56,6 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - setHasOptionsMenu(true) - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced) super.onCreateOptionsMenu(menu, inflater) diff --git a/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl index 5cf8b2a1b..24db01b67 100644 --- a/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl +++ b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl @@ -6,8 +6,20 @@ interface ISafetyNetService { void attest(ISafetyNetCallbacks callbacks, in byte[] nonce) = 0; void attestWithApiKey(ISafetyNetCallbacks callbacks, in byte[] nonce, String apiKey) = 6; void getSharedUuid(ISafetyNetCallbacks callbacks) = 1; - void lookupUri(ISafetyNetCallbacks callbacks, String s1, in int[] threatTypes, int i, String s2) = 2; + void lookupUri(ISafetyNetCallbacks callbacks, String apiKey, in int[] threatTypes, int i, String s2) = 2; void init(ISafetyNetCallbacks callbacks) = 3; void getHarmfulAppsList(ISafetyNetCallbacks callbacks) = 4; void verifyWithRecaptcha(ISafetyNetCallbacks callbacks, String siteKey) = 5; + +// void fun9(ISafetyNetCallbacks callbacks) = 8; +// void fun10(ISafetyNetCallbacks callbacks, String s1, int i1, in byte[] b1) = 9; +// void fun11(int i1, in Bundle b1) = 10; +// void fun12(ISafetyNetCallbacks callbacks) = 11; +// void fun13() = 12; +// void fun14(ISafetyNetCallbacks callbacks) = 13; +// +// void fun18(ISafetyNetCallbacks callbacks, int i1, String s1) = 17; +// void fun19(ISafetyNetCallbacks callbacks, int i1) = 18; +// void removeHarmfulApp(ISafetyNetCallbacks callbacks, String packageName, in byte[] digest) = 19; +// void fun21(ISafetyNetCallbacks callbacks, in Bundle b1) = 20; } diff --git a/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java index f7fb244dc..d4eea9063 100644 --- a/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java @@ -15,7 +15,7 @@ import com.google.android.gms.common.api.CommonStatusCodes; */ public class SafetyNetStatusCodes extends CommonStatusCodes { public static final int SAFE_BROWSING_UNSUPPORTED_THREAT_TYPES = 12000; - public static final int SAFE_BROWSING_MISSING_API_KEYINT = 12001; + public static final int SAFE_BROWSING_MISSING_API_KEY = 12001; public static final int SAFE_BROWSING_API_NOT_AVAILABLE = 12002; public static final int VERIFY_APPS_NOT_AVAILABLE = 12003; public static final int VERIFY_APPS_INTERNAL_ERROR = 12004; diff --git a/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt index f130833dc..a62ece349 100644 --- a/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt +++ b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt @@ -21,6 +21,7 @@ import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.IGmsCallbacks import com.google.android.gms.safetynet.AttestationData import com.google.android.gms.safetynet.RecaptchaResultData +import com.google.android.gms.safetynet.SafeBrowsingData import com.google.android.gms.safetynet.SafetyNetStatusCodes import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks import com.google.android.gms.safetynet.internal.ISafetyNetService @@ -92,7 +93,11 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa is IOException -> SafetyNetStatusCodes.NETWORK_ERROR else -> SafetyNetStatusCodes.ERROR } - callbacks.onAttestationData(Status(code, e.localizedMessage), null) + try { + callbacks.onAttestationData(Status(code, e.localizedMessage), null) + } catch (e: Exception) { + Log.w(TAG, "Exception while sending error", e) + } } } } @@ -106,8 +111,9 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa callbacks.onString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") } - override fun lookupUri(callbacks: ISafetyNetCallbacks, s1: String, threatTypes: IntArray, i: Int, s2: String) { + override fun lookupUri(callbacks: ISafetyNetCallbacks, apiKey: String, threatTypes: IntArray, i: Int, s2: String) { Log.d(TAG, "unimplemented Method: lookupUri") + callbacks.onSafeBrowsingData(Status.SUCCESS, SafeBrowsingData()) } override fun init(callbacks: ISafetyNetCallbacks) { -- GitLab From ebc270c38db1a6f42413dd5ba27c5f18260330f8 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 8 Jul 2022 10:50:49 +0200 Subject: [PATCH 21/26] Add initial implementation for Fido and GmsCompliance APIs --- .../microg/gms/utils/PackageManagerUtils.kt | 32 ++ .../java/org/microg/gms/common/GmsClient.java | 23 +- .../org/microg/gms/utils/ToStringHelper.java | 119 +++++ play-services-core/build.gradle | 4 +- .../src/main/AndroidManifest.xml | 8 - play-services-fido-api/build.gradle | 33 ++ .../src/main/AndroidManifest.xml | 6 + .../gms/fido/fido2/api/IBooleanCallback.aidl | 8 + ...serPublicKeyCredentialCreationOptions.aidl | 3 + ...wserPublicKeyCredentialRequestOptions.aidl | 3 + .../privileged/IFido2PrivilegedCallbacks.aidl | 8 + .../privileged/IFido2PrivilegedService.aidl | 12 + .../android/gms/fido/common/Transport.java | 107 +++++ .../gms/fido/fido2/api/common/Algorithm.java | 20 + .../gms/fido/fido2/api/common/Attachment.java | 81 ++++ .../AttestationConveyancePreference.java | 77 ++++ .../api/common/AuthenticationExtensions.java | 111 +++++ ...AuthenticationExtensionsClientOutputs.java | 93 ++++ .../AuthenticatorAssertionResponse.java | 109 +++++ .../AuthenticatorAttestationResponse.java | 91 ++++ .../common/AuthenticatorErrorResponse.java | 87 ++++ .../api/common/AuthenticatorResponse.java | 20 + .../AuthenticatorSelectionCriteria.java | 102 ++++ ...serPublicKeyCredentialCreationOptions.java | 160 +++++++ ...wserPublicKeyCredentialRequestOptions.java | 160 +++++++ .../api/common/BrowserRequestOptions.java | 23 + .../api/common/COSEAlgorithmIdentifier.java | 81 ++++ .../api/common/CableAuthenticationData.java | 21 + .../common/CableAuthenticationExtension.java | 17 + .../fido/fido2/api/common/EC2Algorithm.java | 49 ++ .../gms/fido/fido2/api/common/ErrorCode.java | 128 ++++++ .../fido2/api/common/FidoAppIdExtension.java | 50 ++ .../fido2/api/common/KeyProtectionTypes.java | 47 ++ .../api/common/MatcherProtectionTypes.java | 31 ++ .../fido2/api/common/PublicKeyCredential.java | 178 +++++++ .../PublicKeyCredentialCreationOptions.java | 289 ++++++++++++ .../common/PublicKeyCredentialDescriptor.java | 91 ++++ .../common/PublicKeyCredentialParameters.java | 85 ++++ .../PublicKeyCredentialRequestOptions.java | 220 +++++++++ .../common/PublicKeyCredentialRpEntity.java | 75 +++ .../api/common/PublicKeyCredentialType.java | 75 +++ .../common/PublicKeyCredentialUserEntity.java | 87 ++++ .../fido/fido2/api/common/RSAAlgorithm.java | 63 +++ .../fido/fido2/api/common/RequestOptions.java | 32 ++ .../fido/fido2/api/common/TokenBinding.java | 179 ++++++++ .../UserVerificationMethodExtension.java | 49 ++ .../api/common/UserVerificationMethods.java | 74 +++ .../common/UserVerificationRequirement.java | 68 +++ .../gms/fido/fido2/api/common/UvmEntries.java | 81 ++++ .../gms/fido/fido2/api/common/UvmEntry.java | 91 ++++ .../SourceDirectTransferResult.java | 42 ++ .../SourceStartDirectTransferOptions.java | 40 ++ .../microg/gms/fido/api/FidoConstants.java | 13 + play-services-fido-core/build.gradle | 68 +++ .../src/main/AndroidManifest.xml | 28 ++ .../gms/fido/core/InternalCredentialStore.kt | 59 +++ .../microg/gms/fido/core/RequestHandling.kt | 434 ++++++++++++++++++ .../core/privileged/Fido2PrivilegedService.kt | 99 ++++ .../gms/fido/core/ui/AuthenticatorActivity.kt | 185 ++++++++ .../core/ui/AuthenticatorActivityFragment.kt | 29 ++ .../ui/AuthenticatorActivityFragmentData.kt | 32 ++ .../org/microg/gms/fido/core/ui/Transport.kt | 13 + .../core/ui/TransportSelectionFragment.kt | 25 + .../gms/fido/core/ui/WelcomeFragment.kt | 35 ++ .../main/res/drawable/ic_fido_bluetooth.xml | 16 + .../main/res/drawable/ic_fido_fingerprint.xml | 16 + .../src/main/res/drawable/ic_fido_key.xml | 16 + .../src/main/res/drawable/ic_fido_nfc.xml | 16 + .../src/main/res/drawable/ic_fido_usb.xml | 16 + .../layout/fido_authenticator_activity.xml | 11 + .../fido_transport_selection_fragment.xml | 218 +++++++++ .../main/res/layout/fido_welcome_fragment.xml | 67 +++ .../res/navigation/nav_fido_authenticator.xml | 68 +++ .../src/main/res/values/strings.xml | 15 + .../src/main/res/values/styles.xml | 19 + play-services-fido/build.gradle | 37 ++ .../src/main/AndroidManifest.xml | 6 + .../com/google/android/gms/fido/Fido.java | 124 +++++ .../gms/fido/fido2/Fido2ApiClient.java | 82 ++++ .../gms/fido/fido2/Fido2PendingIntent.java | 37 ++ .../fido/fido2/Fido2PrivilegedApiClient.java | 144 ++++++ .../SourceDirectTransferClient.java | 49 ++ .../android/gms/fido/u2f/U2fApiClient.java | 67 +++ .../gms/fido/u2f/U2fPendingIntent.java | 39 ++ .../fido/fido2/Fido2PendingIntentImpl.java | 29 ++ .../fido/fido2/Fido2PrivilegedGmsClient.java | 45 ++ play-services-gmscompliance-api/build.gradle | 35 ++ .../src/main/AndroidManifest.xml | 7 + .../GmsDeviceComplianceResponse.aidl | 3 + .../IGmsDeviceComplianceService.aidl | 8 + .../IGmsDeviceComplianceServiceCallback.aidl | 8 + .../GmsDeviceComplianceResponse.java | 21 + play-services-gmscompliance-core/build.gradle | 58 +++ .../src/main/AndroidManifest.xml | 17 + .../GmsDeviceComplianceService.kt | 49 ++ settings.gradle | 5 + 96 files changed, 6094 insertions(+), 17 deletions(-) create mode 100644 play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerUtils.kt create mode 100644 play-services-basement/src/main/java/org/microg/gms/utils/ToStringHelper.java create mode 100644 play-services-fido-api/build.gradle create mode 100644 play-services-fido-api/src/main/AndroidManifest.xml create mode 100644 play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/IBooleanCallback.aidl create mode 100644 play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.aidl create mode 100644 play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.aidl create mode 100644 play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedCallbacks.aidl create mode 100644 play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedService.aidl create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/common/Transport.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Algorithm.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Attachment.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AttestationConveyancePreference.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensions.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorResponse.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorSelectionCriteria.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserRequestOptions.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/COSEAlgorithmIdentifier.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationData.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationExtension.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/EC2Algorithm.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/ErrorCode.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/FidoAppIdExtension.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/KeyProtectionTypes.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/MatcherProtectionTypes.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialDescriptor.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialParameters.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRpEntity.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialType.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialUserEntity.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RSAAlgorithm.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/TokenBinding.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethodExtension.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethods.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationRequirement.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntries.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntry.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceDirectTransferResult.java create mode 100644 play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceStartDirectTransferOptions.java create mode 100644 play-services-fido-api/src/main/java/org/microg/gms/fido/api/FidoConstants.java create mode 100644 play-services-fido-core/build.gradle create mode 100644 play-services-fido-core/src/main/AndroidManifest.xml create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/InternalCredentialStore.kt create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/privileged/Fido2PrivilegedService.kt create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragment.kt create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragmentData.kt create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/Transport.kt create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/TransportSelectionFragment.kt create mode 100644 play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/WelcomeFragment.kt create mode 100644 play-services-fido-core/src/main/res/drawable/ic_fido_bluetooth.xml create mode 100644 play-services-fido-core/src/main/res/drawable/ic_fido_fingerprint.xml create mode 100644 play-services-fido-core/src/main/res/drawable/ic_fido_key.xml create mode 100644 play-services-fido-core/src/main/res/drawable/ic_fido_nfc.xml create mode 100644 play-services-fido-core/src/main/res/drawable/ic_fido_usb.xml create mode 100644 play-services-fido-core/src/main/res/layout/fido_authenticator_activity.xml create mode 100644 play-services-fido-core/src/main/res/layout/fido_transport_selection_fragment.xml create mode 100644 play-services-fido-core/src/main/res/layout/fido_welcome_fragment.xml create mode 100644 play-services-fido-core/src/main/res/navigation/nav_fido_authenticator.xml create mode 100644 play-services-fido-core/src/main/res/values/strings.xml create mode 100644 play-services-fido-core/src/main/res/values/styles.xml create mode 100644 play-services-fido/build.gradle create mode 100644 play-services-fido/src/main/AndroidManifest.xml create mode 100644 play-services-fido/src/main/java/com/google/android/gms/fido/Fido.java create mode 100644 play-services-fido/src/main/java/com/google/android/gms/fido/fido2/Fido2ApiClient.java create mode 100644 play-services-fido/src/main/java/com/google/android/gms/fido/fido2/Fido2PendingIntent.java create mode 100644 play-services-fido/src/main/java/com/google/android/gms/fido/fido2/Fido2PrivilegedApiClient.java create mode 100644 play-services-fido/src/main/java/com/google/android/gms/fido/sourcedevice/SourceDirectTransferClient.java create mode 100644 play-services-fido/src/main/java/com/google/android/gms/fido/u2f/U2fApiClient.java create mode 100644 play-services-fido/src/main/java/com/google/android/gms/fido/u2f/U2fPendingIntent.java create mode 100644 play-services-fido/src/main/java/org/microg/gms/fido/fido2/Fido2PendingIntentImpl.java create mode 100644 play-services-fido/src/main/java/org/microg/gms/fido/fido2/Fido2PrivilegedGmsClient.java create mode 100644 play-services-gmscompliance-api/build.gradle create mode 100644 play-services-gmscompliance-api/src/main/AndroidManifest.xml create mode 100644 play-services-gmscompliance-api/src/main/aidl/com/google/android/gms/gmscompliance/GmsDeviceComplianceResponse.aidl create mode 100644 play-services-gmscompliance-api/src/main/aidl/com/google/android/gms/gmscompliance/IGmsDeviceComplianceService.aidl create mode 100644 play-services-gmscompliance-api/src/main/aidl/com/google/android/gms/gmscompliance/IGmsDeviceComplianceServiceCallback.aidl create mode 100644 play-services-gmscompliance-api/src/main/java/com/google/android/gms/gmscompliance/GmsDeviceComplianceResponse.java create mode 100644 play-services-gmscompliance-core/build.gradle create mode 100644 play-services-gmscompliance-core/src/main/AndroidManifest.xml create mode 100644 play-services-gmscompliance-core/src/main/kotlin/org/microg/gms/gmscompliance/GmsDeviceComplianceService.kt diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerUtils.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerUtils.kt new file mode 100644 index 000000000..e4fbc7418 --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerUtils.kt @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.utils + +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.NameNotFoundException +import android.content.pm.Signature +import android.util.Base64 +import java.security.MessageDigest + +fun PackageManager.getSignatures(packageName: String): Array = try { + getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures +} catch (e: NameNotFoundException) { + emptyArray() +} + +fun PackageManager.getApplicationLabel(packageName: String): CharSequence = try { + getApplicationLabel(getApplicationInfo(packageName, 0)) +} catch (e: Exception) { + packageName +} + +fun ByteArray.toBase64(vararg flags: Int): String = Base64.encodeToString(this, flags.fold(0) { a, b -> a or b }) + +fun PackageManager.getFirstSignatureDigest(packageName: String, md: String): ByteArray? = + getSignatures(packageName).firstOrNull()?.digest(md) + +fun Signature.digest(md: String): ByteArray = MessageDigest.getInstance(md).digest(toByteArray()) diff --git a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java index a03b2c3bd..9558ebe30 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java +++ b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java @@ -27,6 +27,7 @@ import android.os.RemoteException; import android.util.Log; import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.internal.ConnectionInfo; import com.google.android.gms.common.internal.GetServiceRequest; import com.google.android.gms.common.internal.IGmsCallbacks; @@ -166,17 +167,23 @@ public abstract class GmsClient implements ApiClient { @Override public void onPostInitComplete(int statusCode, IBinder binder, Bundle params) throws RemoteException { - synchronized (GmsClient.this) { - if (state == ConnectionState.DISCONNECTING) { + if (statusCode != CommonStatusCodes.SUCCESS) { + state = ConnectionState.CONNECTED; + disconnect(); + connectionFailedListener.onConnectionFailed(new ConnectionResult(statusCode)); + } else { + synchronized (GmsClient.this) { + if (state == ConnectionState.DISCONNECTING) { + state = ConnectionState.CONNECTED; + disconnect(); + return; + } state = ConnectionState.CONNECTED; - disconnect(); - return; + serviceInterface = interfaceFromBinder(binder); } - state = ConnectionState.CONNECTED; - serviceInterface = interfaceFromBinder(binder); + Log.d(TAG, "GmsCallbacks : onPostInitComplete(" + serviceInterface + ")"); + callbacks.onConnected(params); } - Log.d(TAG, "GmsCallbacks : onPostInitComplete(" + serviceInterface + ")"); - callbacks.onConnected(params); } @Override diff --git a/play-services-basement/src/main/java/org/microg/gms/utils/ToStringHelper.java b/play-services-basement/src/main/java/org/microg/gms/utils/ToStringHelper.java new file mode 100644 index 000000000..deb4e0f98 --- /dev/null +++ b/play-services-basement/src/main/java/org/microg/gms/utils/ToStringHelper.java @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.utils; + +import android.util.Base64; + +public class ToStringHelper { + private StringBuilder sb; + private boolean hasField; + private boolean hasValue; + private boolean hasEnd; + + public ToStringHelper(String name) { + this.sb = new StringBuilder(name).append("["); + } + + public static ToStringHelper name(String name) { + return new ToStringHelper(name); + } + + public ToStringHelper value(String val) { + if (!hasField) { + if (hasValue) sb.append(','); + sb.append(val); + hasValue = true; + } + return this; + } + + public ToStringHelper value(long val) { + return value(Long.toString(val)); + } + + public ToStringHelper value(double val) { + return value(Double.toString(val)); + } + + public ToStringHelper value(Object val) { + if (val instanceof Long) value((long) val); + if (val instanceof Double) value((double) val); + return value(val, false); + } + + public ToStringHelper value(Object val, boolean forceNull) { + if (val == null && !forceNull) return this; + return value(val == null ? "null" : val.toString()); + } + + public ToStringHelper value(byte[] val) { + return value(val, false); + } + + public ToStringHelper value(byte[] val, boolean forceNull) { + if (val == null && !forceNull) return this; + return value(val == null ? "null" : Base64.encodeToString(val, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE)); + } + + private ToStringHelper fieldUnquoted(String name, String val) { + if (hasValue || hasField) sb.append(", "); + sb.append(name).append('=').append(val); + hasField = true; + return this; + } + + public ToStringHelper field(String name, String val) { + return field(name, val, false); + } + + public ToStringHelper field(String name, String val, boolean forceNull) { + if (val == null && !forceNull) return this; + if (val == null) return fieldUnquoted(name, "null"); + if (hasValue || hasField) sb.append(", "); + sb.append(name).append("=\"").append(val.replace("\"", "\\\"")).append('"'); + hasField = true; + return this; + } + + public ToStringHelper field(String name, long val) { + return fieldUnquoted(name, Long.toString(val)); + } + + public ToStringHelper field(String name, double val) { + return fieldUnquoted(name, Double.toString(val)); + } + + public ToStringHelper field(String name, boolean val) { + return fieldUnquoted(name, Boolean.toString(val)); + } + + public ToStringHelper field(String name, Object val) { + if (val instanceof Long) return field(name, (long) val); + if (val instanceof Double) return field(name, (double) val); + if (val instanceof Boolean) return field(name, (boolean) val); + return field(name, val, false); + } + + public ToStringHelper field(String name, Object val, boolean forceNull) { + if (val == null && !forceNull) return this; + return fieldUnquoted(name, val == null ? "null" : val.toString()); + } + + public ToStringHelper field(String name, byte[] val) { + return field(name, val, false); + } + + public ToStringHelper field(String name, byte[] val, boolean forceNull) { + if (val == null && !forceNull) return this; + return fieldUnquoted(name, val == null ? "null" : Base64.encodeToString(val, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE)); + } + + public String end() { + if (!hasEnd) sb.append(']'); + hasEnd = true; + return sb.toString(); + } +} diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 75458ee45..996d06ac9 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -35,6 +35,8 @@ dependencies { implementation project(':play-services-cronet-core') implementation project(':play-services-droidguard-core') implementation project(':play-services-droidguard-core-ui') + implementation project(':play-services-fido-core') + implementation project(':play-services-gmscompliance-core') implementation project(':play-services-location-core') withNearbyImplementation project(':play-services-nearby-core') withNearbyImplementation project(':play-services-nearby-core-ui') @@ -77,7 +79,7 @@ dependencies { } android { - compileSdkVersion androidCompileSdk() + compileSdkVersion androidCompileSdk buildToolsVersion "$androidBuildVersionTools" defaultConfig { diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 0649e1bb3..b0bb6b183 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -530,12 +530,6 @@ android:process=":ui" android:theme="@style/Theme.AppCompat.DayNight.Dialog.Alert.NoActionBar" /> - - - @@ -781,7 +774,6 @@ - diff --git a/play-services-fido-api/build.gradle b/play-services-fido-api/build.gradle new file mode 100644 index 000000000..b8f659f52 --- /dev/null +++ b/play-services-fido-api/build.gradle @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG API for play-services-fido' + +dependencies { + api project(':play-services-basement') + api project(':play-services-base-api') +} diff --git a/play-services-fido-api/src/main/AndroidManifest.xml b/play-services-fido-api/src/main/AndroidManifest.xml new file mode 100644 index 000000000..00715eb7e --- /dev/null +++ b/play-services-fido-api/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/IBooleanCallback.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/IBooleanCallback.aidl new file mode 100644 index 000000000..1813d42c6 --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/IBooleanCallback.aidl @@ -0,0 +1,8 @@ +package com.google.android.gms.fido.fido2.api; + +import com.google.android.gms.common.api.Status; + +interface IBooleanCallback { + void onBoolean(boolean value) = 0; + void onError(in Status status) = 1; +} diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.aidl new file mode 100644 index 000000000..6e38baa9f --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.fido.fido2.api.common; + +parcelable BrowserPublicKeyCredentialCreationOptions; diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.aidl new file mode 100644 index 000000000..0d338e967 --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.fido.fido2.api.common; + +parcelable BrowserPublicKeyCredentialRequestOptions; diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedCallbacks.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedCallbacks.aidl new file mode 100644 index 000000000..6eca9dfa7 --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedCallbacks.aidl @@ -0,0 +1,8 @@ +package com.google.android.gms.fido.fido2.internal.privileged; + +import android.app.PendingIntent; +import com.google.android.gms.common.api.Status; + +interface IFido2PrivilegedCallbacks { + void onPendingIntent(in Status status, in PendingIntent pendingIntent); +} diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedService.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedService.aidl new file mode 100644 index 000000000..b3f86dcc5 --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedService.aidl @@ -0,0 +1,12 @@ +package com.google.android.gms.fido.fido2.internal.privileged; + +import com.google.android.gms.fido.fido2.internal.privileged.IFido2PrivilegedCallbacks; +import com.google.android.gms.fido.fido2.api.IBooleanCallback; +import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialCreationOptions; +import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialRequestOptions; + +interface IFido2PrivilegedService { + void register(IFido2PrivilegedCallbacks callbacks, in BrowserPublicKeyCredentialCreationOptions options) = 0; + void sign(IFido2PrivilegedCallbacks callbacks, in BrowserPublicKeyCredentialRequestOptions options) = 1; + void isUserVerifyingPlatformAuthenticatorAvailable(IBooleanCallback callbacks) = 2; +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/common/Transport.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/common/Transport.java new file mode 100644 index 000000000..ccbcdcfae --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/common/Transport.java @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.common; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.microg.gms.common.PublicApi; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * The transport between the authenticator and the client. + */ +@PublicApi +public enum Transport implements Parcelable { + BLUETOOTH_CLASSIC("bt"), + BLUETOOTH_LOW_ENERGY("ble"), + NFC("nfc"), + USB("usb"), + INTERNAL("internal"), + @PublicApi(exclude = true) + CABLE("cable"); + + private final String value; + + Transport(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static Transport fromString(String transport) throws UnsupportedTransportException { + for (Transport value : values()) { + if (value.value.equals(transport)) return value; + } + throw new UnsupportedTransportException("Transport " + transport + " not supported"); + } + + @PublicApi(exclude = true) + public static List parseTransports(JSONArray jsonArray) throws JSONException { + if (jsonArray == null) return null; + Set set = new HashSet<>(); + for (int i = 0; i < jsonArray.length(); i++) { + String transport = jsonArray.getString(i); + if (transport != null && !transport.isEmpty()) { + try { + set.add(fromString(transport)); + } catch (UnsupportedTransportException e) { + Log.w("Transport", "Ignoring unrecognized transport " + transport); + } + } + } + return new ArrayList<>(set); + } + + public static Creator CREATOR = new Creator() { + @Override + public Transport createFromParcel(Parcel source) { + try { + return Transport.fromString(source.readString()); + } catch (UnsupportedTransportException e) { + throw new RuntimeException(e); + } + } + + @Override + public Transport[] newArray(int size) { + return new Transport[size]; + } + }; + + /** + * Exception thrown when an unsupported or unrecognized transport is encountered. + */ + public static class UnsupportedTransportException extends Exception { + public UnsupportedTransportException(String errorMessage) { + super(errorMessage); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Algorithm.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Algorithm.java new file mode 100644 index 000000000..3b03044fa --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Algorithm.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +/** + * An interface for an algorithm used in public key encryption. All implementations must conform to the guidelines + * regarding algorithm registrations in RFC8152. + */ +public interface Algorithm { + /** + * Gets the COSE value for the algorithm used in the encryption of the credential. + */ + int getAlgoValue(); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Attachment.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Attachment.java new file mode 100644 index 000000000..4f031468c --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Attachment.java @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.android.gms.fido.common.Transport; + +import org.microg.gms.common.PublicApi; + +/** + * Clients may communicate with authenticators using a variety of mechanisms. We define authenticators that are + * part of the client's platform as having a platform attachment, and refer to them as platform authenticators. + * While those that are reachable via cross-platform transport protocols are defined as having cross-platform + * attachment, and refer to them as roaming authenticators. + */ +public enum Attachment implements Parcelable { + PLATFORM("platform"), + CROSS_PLATFORM("cross-platform"); + + private final String value; + + Attachment(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static Attachment fromString(String attachment) throws UnsupportedAttachmentException { + for (Attachment value : values()) { + if (value.value.equals(attachment)) return value; + } + throw new UnsupportedAttachmentException("Attachment " + attachment + " not supported"); + } + + public static Creator CREATOR = new Creator() { + @Override + public Attachment createFromParcel(Parcel source) { + try { + return Attachment.fromString(source.readString()); + } catch (Attachment.UnsupportedAttachmentException e) { + throw new RuntimeException(e); + } + } + + @Override + public Attachment[] newArray(int size) { + return new Attachment[size]; + } + }; + + /** + * Exception thrown when an unsupported or unrecognized attachment is encountered. + */ + public static class UnsupportedAttachmentException extends Exception { + public UnsupportedAttachmentException(String message) { + super(message); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AttestationConveyancePreference.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AttestationConveyancePreference.java new file mode 100644 index 000000000..d23c8c804 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AttestationConveyancePreference.java @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +/** + * An enum describing the relying party's preference for attestation conveyance. + */ +public enum AttestationConveyancePreference implements Parcelable { + NONE("none"), + INDIRECT("indirect"), + DIRECT("direct"); + + private final String value; + + AttestationConveyancePreference(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static AttestationConveyancePreference fromString(String attachment) throws UnsupportedAttestationConveyancePreferenceException { + for (AttestationConveyancePreference value : values()) { + if (value.value.equals(attachment)) return value; + } + throw new UnsupportedAttestationConveyancePreferenceException("Attestation conveyance preference " + attachment + " not supported"); + } + + public static Creator CREATOR = new Creator() { + @Override + public AttestationConveyancePreference createFromParcel(Parcel source) { + try { + return AttestationConveyancePreference.fromString(source.readString()); + } catch (UnsupportedAttestationConveyancePreferenceException e) { + throw new RuntimeException(e); + } + } + + @Override + public AttestationConveyancePreference[] newArray(int size) { + return new AttestationConveyancePreference[size]; + } + }; + + /** + * Exception thrown when an unsupported or unrecognized attestation conveyance preference is encountered. + */ + public static class UnsupportedAttestationConveyancePreferenceException extends Exception { + public UnsupportedAttestationConveyancePreferenceException(String message) { + super(message); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensions.java new file mode 100644 index 000000000..e5acf95c4 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensions.java @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Represents extensions that can be passed into FIDO2 APIs. This container class corresponds to the additional + * parameters requesting additional processing by authenticators. + *

+ * Note that rather than accepting arbitrary objects as specified in WebAuthn, this class requires a structured entry + * for each supported extension. + */ +@PublicApi +public class AuthenticationExtensions extends AutoSafeParcelable { + @Field(2) + private FidoAppIdExtension fidoAppIdExtension; + @Field(3) + private CableAuthenticationExtension cableAuthenticationExtension; + @Field(4) + private UserVerificationMethodExtension userVerificationMethodExtension; + + public FidoAppIdExtension getFidoAppIdExtension() { + return fidoAppIdExtension; + } + + public UserVerificationMethodExtension getUserVerificationMethodExtension() { + return userVerificationMethodExtension; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticationExtensions)) return false; + + AuthenticationExtensions that = (AuthenticationExtensions) o; + + if (fidoAppIdExtension != null ? !fidoAppIdExtension.equals(that.fidoAppIdExtension) : that.fidoAppIdExtension != null) + return false; + if (cableAuthenticationExtension != null ? !cableAuthenticationExtension.equals(that.cableAuthenticationExtension) : that.cableAuthenticationExtension != null) + return false; + return userVerificationMethodExtension != null ? userVerificationMethodExtension.equals(that.userVerificationMethodExtension) : that.userVerificationMethodExtension == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{fidoAppIdExtension, cableAuthenticationExtension, userVerificationMethodExtension}); + } + + @Override + public String toString() { + return "AuthenticationExtensions{" + + "fidoAppIdExtension=" + fidoAppIdExtension + + ", cableAuthenticationExtension=" + cableAuthenticationExtension + + ", userVerificationMethodExtension=" + userVerificationMethodExtension + + '}'; + } + + /** + * Builder for {@link AuthenticationExtensions}. + */ + public static class Builder { + private FidoAppIdExtension fidoAppIdExtension; + private UserVerificationMethodExtension userVerificationMethodExtension; + + /** + * The constructor of {@link AuthenticationExtensions.Builder}. + */ + public Builder() { + } + + /** + * Sets the App ID extension, which allows for authentication of U2F authenticators previously registered + * under the supplied App ID. + */ + public Builder setFido2Extension(FidoAppIdExtension appIdExtension) { + this.fidoAppIdExtension = appIdExtension; + return this; + } + + /** + * Sets the User Verification Method extension, which allows the relying party to ascertain up to three + * authentication methods that were used. + */ + public Builder setUserVerificationMethodExtension(UserVerificationMethodExtension userVerificationMethodExtension) { + this.userVerificationMethodExtension = userVerificationMethodExtension; + return this; + } + + /** + * Builds the {@link AuthenticationExtensions} object. + */ + public AuthenticationExtensions build() { + AuthenticationExtensions extensions = new AuthenticationExtensions(); + extensions.fidoAppIdExtension = fidoAppIdExtension; + extensions.userVerificationMethodExtension = userVerificationMethodExtension; + return extensions; + } + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticationExtensions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java new file mode 100644 index 000000000..84bf5770e --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * This container class represents client output for extensions that can be passed into FIDO2 APIs. + */ +@PublicApi +public class AuthenticationExtensionsClientOutputs extends AutoSafeParcelable { + @Field(1) + private UvmEntries uvmEntries; + + public UvmEntries getUvmEntries() { + return uvmEntries; + } + + /** + * Serializes the {@link AuthenticationExtensionsClientOutputs} to bytes. + * Use {@link #deserializeFromBytes(byte[])} to deserialize. + */ + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + /** + * De-serializes the {@link AuthenticationExtensionsClientOutputs} from bytes, reversing {@link #serializeToBytes()}. + * + * @return The deserialized {@link AuthenticationExtensionsClientOutputs} + */ + public static AuthenticationExtensionsClientOutputs deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticationExtensionsClientOutputs)) return false; + + AuthenticationExtensionsClientOutputs that = (AuthenticationExtensionsClientOutputs) o; + + return uvmEntries != null ? uvmEntries.equals(that.uvmEntries) : that.uvmEntries == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{uvmEntries}); + } + + /** + * Builder for {@link AuthenticationExtensionsClientOutputs}. + */ + public static class Builder { + private UvmEntries uvmEntries; + + /** + * The constructor of {@link AuthenticationExtensionsClientOutputs.Builder}. + */ + public Builder() { + } + + /** + * Sets the User Verification Method extension, which allows the relying party to ascertain up to three + * authentication methods that were used. + */ + public Builder setUserVerificationMethodEntries(UvmEntries uvmEntries) { + this.uvmEntries = uvmEntries; + return this; + } + + /** + * Builds the {@link AuthenticationExtensionsClientOutputs} object. + */ + public AuthenticationExtensionsClientOutputs build() { + AuthenticationExtensionsClientOutputs extensions = new AuthenticationExtensionsClientOutputs(); + extensions.uvmEntries = uvmEntries; + return extensions; + } + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticationExtensionsClientOutputs.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java new file mode 100644 index 000000000..1dc01d812 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * This structure contains cryptographic signatures produced by scoped credentials that provides proof of possession + * of a private key as well as evidence of user consent to a specific transaction. + */ +@PublicApi +public class AuthenticatorAssertionResponse extends AuthenticatorResponse { + @Field(2) + private byte[] keyHandle; + @Field(3) + private byte[] clientDataJSON; + @Field(4) + private byte[] authenticatorData; + @Field(5) + private byte[] signature; + @Field(6) + private byte[] userHandle; + + private AuthenticatorAssertionResponse() {} + + public AuthenticatorAssertionResponse(byte[] keyHandle, byte[] clientDataJSON, byte[] authenticatorData, byte[] signature, byte[] userHandle) { + this.keyHandle = keyHandle; + this.clientDataJSON = clientDataJSON; + this.authenticatorData = authenticatorData; + this.signature = signature; + this.userHandle = userHandle; + } + + public byte[] getAuthenticatorData() { + return authenticatorData; + } + + @Override + public byte[] getClientDataJSON() { + return clientDataJSON; + } + + /** + * @deprecated use {@link PublicKeyCredential#getRawId()} instead + */ + @Deprecated + public byte[] getKeyHandle() { + return keyHandle; + } + + public byte[] getSignature() { + return signature; + } + + public byte[] getUserHandle() { + return userHandle; + } + + @Override + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticatorAssertionResponse)) return false; + + AuthenticatorAssertionResponse that = (AuthenticatorAssertionResponse) o; + + if (!Arrays.equals(keyHandle, that.keyHandle)) return false; + if (!Arrays.equals(clientDataJSON, that.clientDataJSON)) return false; + if (!Arrays.equals(authenticatorData, that.authenticatorData)) return false; + if (!Arrays.equals(signature, that.signature)) return false; + return Arrays.equals(userHandle, that.userHandle); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{Arrays.hashCode(keyHandle), Arrays.hashCode(clientDataJSON), Arrays.hashCode(authenticatorData), Arrays.hashCode(signature), Arrays.hashCode(userHandle)}); + } + + @Override + public String toString() { + return ToStringHelper.name("AuthenticatorAssertionResponse") + .field("keyHandle", keyHandle) + .field("clientDataJSON", clientDataJSON) + .field("authenticatorData", authenticatorData) + .field("signature", signature) + .field("userHandle", userHandle) + .end(); + } + + public static AuthenticatorAssertionResponse deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticatorAssertionResponse.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java new file mode 100644 index 000000000..174c5aa07 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * Represents a newly-created scoped credential, aka the response from a registration request. + */ +@PublicApi +public class AuthenticatorAttestationResponse extends AuthenticatorResponse { + @Field(2) + private byte[] keyHandle; + @Field(3) + private byte[] clientDataJSON; + @Field(4) + private byte[] attestationObject; + + private AuthenticatorAttestationResponse() {} + + @PublicApi(exclude = true) + public AuthenticatorAttestationResponse(byte[] keyHandle, byte[] clientDataJSON, byte[] attestationObject) { + this.keyHandle = keyHandle; + this.clientDataJSON = clientDataJSON; + this.attestationObject = attestationObject; + } + + public byte[] getAttestationObject() { + return attestationObject; + } + + @Override + public byte[] getClientDataJSON() { + return clientDataJSON; + } + + /** + * @deprecated use {@link PublicKeyCredential#getRawId()} instead + */ + @Deprecated + public byte[] getKeyHandle() { + return keyHandle; + } + + @Override + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticatorAttestationResponse)) return false; + + AuthenticatorAttestationResponse that = (AuthenticatorAttestationResponse) o; + + if (!Arrays.equals(keyHandle, that.keyHandle)) return false; + if (!Arrays.equals(clientDataJSON, that.clientDataJSON)) return false; + return Arrays.equals(attestationObject, that.attestationObject); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{Arrays.hashCode(keyHandle), Arrays.hashCode(clientDataJSON), Arrays.hashCode(attestationObject)}); + } + + @Override + public String toString() { + return ToStringHelper.name("AuthenticatorAttestationResponse") + .field("keyHandle", keyHandle) + .field("clientDataJSON", clientDataJSON) + .field("attestationObject", attestationObject) + .end(); + } + + public static AuthenticatorAttestationResponse deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticatorAttestationResponse.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java new file mode 100644 index 000000000..1853902ac --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * The response after an error occurred. + */ +@PublicApi +public class AuthenticatorErrorResponse extends AuthenticatorResponse { + @Field(2) + private ErrorCode errorCode; + @Field(3) + private String errorMessage; + + private AuthenticatorErrorResponse() { + } + + @PublicApi(exclude = true) + public AuthenticatorErrorResponse(ErrorCode errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + @Override + public byte[] getClientDataJSON() { + throw new UnsupportedOperationException(); + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public int getErrorCodeAsInt() { + return errorCode.getCode(); + } + + public String getErrorMessage() { + return errorMessage; + } + + @Override + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticatorErrorResponse)) return false; + + AuthenticatorErrorResponse that = (AuthenticatorErrorResponse) o; + + if (errorCode != null ? !errorCode.equals(that.errorCode) : that.errorCode != null) return false; + return errorMessage != null ? errorMessage.equals(that.errorMessage) : that.errorMessage == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{errorCode, errorMessage}); + } + + @Override + public String toString() { + return ToStringHelper.name("AuthenticatorErrorResponse") + .value(errorCode.name()) + .value(errorMessage) + .end(); + } + + public static AuthenticatorErrorResponse deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticatorErrorResponse.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorResponse.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorResponse.java new file mode 100644 index 000000000..0a904f92e --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorResponse.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.safeparcel.AutoSafeParcelable; + +/** + * Authenticators respond to relying party requests by returning an object derived from this interface. + */ +public abstract class AuthenticatorResponse extends AutoSafeParcelable { + public abstract byte[] getClientDataJSON(); + + public abstract byte[] serializeToBytes(); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorSelectionCriteria.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorSelectionCriteria.java new file mode 100644 index 000000000..dd93d0bd3 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorSelectionCriteria.java @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Relying Parties may use {@link AuthenticatorSelectionCriteria} to specify their requirements regarding authenticator + * attributes. + */ +public class AuthenticatorSelectionCriteria extends AutoSafeParcelable { + @Field(2) + private Attachment attachment; + @Field(3) + private Boolean requireResidentKey; + @Field(4) + private UserVerificationRequirement requireUserVerification; + + public Attachment getAttachment() { + return attachment; + } + + public String getAttachmentAsString() { + if (this.attachment == null) { + return null; + } + return attachment.toString(); + } + + public Boolean getRequireResidentKey() { + return requireResidentKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticatorSelectionCriteria)) return false; + + AuthenticatorSelectionCriteria that = (AuthenticatorSelectionCriteria) o; + + if (attachment != that.attachment) return false; + if (requireResidentKey != null ? !requireResidentKey.equals(that.requireResidentKey) : that.requireResidentKey != null) + return false; + return requireUserVerification == that.requireUserVerification; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{attachment, requireResidentKey, requireUserVerification}); + } + + @Override + public String toString() { + return ToStringHelper.name("AuthenticatorSelectionCriteria") + .field("attachment", attachment) + .field("requireResidentKey", requireResidentKey) + .field("requireUserVerification", requireUserVerification) + .end(); + } + + /** + * Builder for {@link AuthenticatorSelectionCriteria}. + */ + public static class Builder { + private Attachment attachment; + private Boolean requireResidentKey; + + /** + * Sets the attachment to use for this session. + */ + public Builder setAttachment(Attachment attachment) { + this.attachment = attachment; + return this; + } + + /** + * Sets whether the key created will be a resident key. + */ + public Builder setRequireResidentKey(Boolean requireResidentKey) { + this.requireResidentKey = requireResidentKey; + return this; + } + + public AuthenticatorSelectionCriteria build() { + AuthenticatorSelectionCriteria criteria = new AuthenticatorSelectionCriteria(); + criteria.attachment = attachment; + criteria.requireResidentKey = requireResidentKey; + return criteria; + } + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticatorSelectionCriteria.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java new file mode 100644 index 000000000..ffb4a620b --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java @@ -0,0 +1,160 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.net.Uri; +import android.util.Base64; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * Parameters to a make credential request from a Web browser. + */ +@PublicApi +public class BrowserPublicKeyCredentialCreationOptions extends BrowserRequestOptions { + @Field(2) + private PublicKeyCredentialCreationOptions delegate; + @Field(3) + private Uri origin; + @Field(4) + private byte[] clientDataHash; + + @Override + public AuthenticationExtensions getAuthenticationExtensions() { + return delegate.getAuthenticationExtensions(); + } + + @Override + public byte[] getChallenge() { + return delegate.getChallenge(); + } + + @Override + public byte[] getClientDataHash() { + return clientDataHash; + } + + @Override + public Uri getOrigin() { + return origin; + } + + public PublicKeyCredentialCreationOptions getPublicKeyCredentialCreationOptions() { + return delegate; + } + + @Override + public Integer getRequestId() { + return delegate.getRequestId(); + } + + @Override + public Double getTimeoutSeconds() { + return delegate.getTimeoutSeconds(); + } + + @Override + public TokenBinding getTokenBinding() { + return delegate.getTokenBinding(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BrowserPublicKeyCredentialCreationOptions)) return false; + + BrowserPublicKeyCredentialCreationOptions that = (BrowserPublicKeyCredentialCreationOptions) o; + + if (delegate != null ? !delegate.equals(that.delegate) : that.delegate != null) return false; + if (origin != null ? !origin.equals(that.origin) : that.origin != null) return false; + return Arrays.equals(clientDataHash, that.clientDataHash); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{delegate, origin, Arrays.hashCode(clientDataHash)}); + } + + @Override + public String toString() { + return ToStringHelper.name("BrowserPublicKeyCredentialCreationOptions") + .value(delegate) + .field("origin", origin) + .field("clientDataHash", clientDataHash) + .end(); + } + + /** + * Builder for {@link BrowserPublicKeyCredentialCreationOptions}. + */ + public static class Builder { + private PublicKeyCredentialCreationOptions delegate; + private Uri origin; + private byte[] clientDataHash; + + /** + * The constructor of {@link BrowserPublicKeyCredentialCreationOptions.Builder}. + */ + public Builder() { + } + + /** + * Sets a clientDataHash value to sign over in place of assembling and hashing clientDataJSON during the + * signature request. + *

+ * Note: This is optional and only provided for contexts where the unhashed information necessary to assemble + * WebAuthn clientDataJSON is not available. If set, the resulting {@link AuthenticatorAssertionResponse} will + * return an invalid value for {@code getClientDataJSON()}. Generally, browser clients should use + * {@link PublicKeyCredentialCreationOptions.Builder#setChallenge(byte[])} instead. + * + * @return + */ + public BrowserPublicKeyCredentialCreationOptions.Builder setClientDataHash(byte[] clientDataHash) { + this.clientDataHash = clientDataHash; + return this; + } + + /** + * Sets the origin on whose behalf the calling browser is requesting a registration operation. + */ + public BrowserPublicKeyCredentialCreationOptions.Builder setOrigin(Uri origin) { + this.origin = origin; + return this; + } + + /** + * Sets the parameters to dictate the client behavior during this registration session. + */ + public BrowserPublicKeyCredentialCreationOptions.Builder setPublicKeyCredentialRequestOptions(PublicKeyCredentialCreationOptions publicKeyCredentialCreationOptions) { + this.delegate = publicKeyCredentialCreationOptions; + return this; + } + + /** + * Builds the {@link BrowserPublicKeyCredentialRequestOptions} object. + */ + public BrowserPublicKeyCredentialCreationOptions build() { + BrowserPublicKeyCredentialCreationOptions options = new BrowserPublicKeyCredentialCreationOptions(); + options.delegate = delegate; + options.origin = origin; + options.clientDataHash = clientDataHash; + return options; + } + } + + public static BrowserPublicKeyCredentialCreationOptions deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(BrowserPublicKeyCredentialCreationOptions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java new file mode 100644 index 000000000..848112284 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java @@ -0,0 +1,160 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.net.Uri; +import android.util.Base64; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * Parameters for a signature request from a Web Browser. + */ +@PublicApi +public class BrowserPublicKeyCredentialRequestOptions extends BrowserRequestOptions { + @Field(2) + private PublicKeyCredentialRequestOptions delegate; + @Field(3) + private Uri origin; + @Field(4) + private byte[] clientDataHash; + + @Override + public AuthenticationExtensions getAuthenticationExtensions() { + return delegate.getAuthenticationExtensions(); + } + + @Override + public byte[] getChallenge() { + return delegate.getChallenge(); + } + + @Override + public byte[] getClientDataHash() { + return clientDataHash; + } + + @Override + public Uri getOrigin() { + return origin; + } + + public PublicKeyCredentialRequestOptions getPublicKeyCredentialRequestOptions() { + return delegate; + } + + @Override + public Integer getRequestId() { + return delegate.getRequestId(); + } + + @Override + public Double getTimeoutSeconds() { + return delegate.getTimeoutSeconds(); + } + + @Override + public TokenBinding getTokenBinding() { + return delegate.getTokenBinding(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BrowserPublicKeyCredentialRequestOptions)) return false; + + BrowserPublicKeyCredentialRequestOptions that = (BrowserPublicKeyCredentialRequestOptions) o; + + if (delegate != null ? !delegate.equals(that.delegate) : that.delegate != null) return false; + if (origin != null ? !origin.equals(that.origin) : that.origin != null) return false; + return Arrays.equals(clientDataHash, that.clientDataHash); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{delegate, origin, Arrays.hashCode(clientDataHash)}); + } + + @Override + public String toString() { + return ToStringHelper.name("BrowserPublicKeyCredentialRequestOptions") + .value(delegate) + .field("origin", origin) + .field("clientDataHash", clientDataHash) + .end(); + } + + /** + * Builder for {@link BrowserPublicKeyCredentialRequestOptions}. + */ + public static class Builder { + private PublicKeyCredentialRequestOptions delegate; + private Uri origin; + private byte[] clientDataHash; + + /** + * The constructor of {@link BrowserPublicKeyCredentialRequestOptions.Builder}. + */ + public Builder() { + } + + /** + * Sets a clientDataHash value to sign over in place of assembling and hashing clientDataJSON during the + * signature request. + *

+ * Note: This is optional and only provided for contexts where the unhashed information necessary to assemble + * WebAuthn clientDataJSON is not available. If set, the resulting {@link AuthenticatorAssertionResponse} will + * return an invalid value for {@code getClientDataJSON()}. Generally, browser clients should use + * {@link PublicKeyCredentialRequestOptions.Builder#setChallenge(byte[])} instead. + * + * @return + */ + public Builder setClientDataHash(byte[] clientDataHash) { + this.clientDataHash = clientDataHash; + return this; + } + + /** + * Sets the origin on whose behalf the calling browser is requesting an authentication operation. + */ + public Builder setOrigin(Uri origin) { + this.origin = origin; + return this; + } + + /** + * Sets the parameters to dictate client behavior during this authentication session. + */ + public Builder setPublicKeyCredentialRequestOptions(PublicKeyCredentialRequestOptions publicKeyCredentialRequestOptions) { + this.delegate = publicKeyCredentialRequestOptions; + return this; + } + + /** + * Builds the {@link BrowserPublicKeyCredentialRequestOptions} object. + */ + public BrowserPublicKeyCredentialRequestOptions build() { + BrowserPublicKeyCredentialRequestOptions options = new BrowserPublicKeyCredentialRequestOptions(); + options.delegate = delegate; + options.origin = origin; + options.clientDataHash = clientDataHash; + return options; + } + } + + public static BrowserPublicKeyCredentialRequestOptions deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(BrowserPublicKeyCredentialRequestOptions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserRequestOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserRequestOptions.java new file mode 100644 index 000000000..407e9a785 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserRequestOptions.java @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.net.Uri; + +/** + * An abstract class representing browser-based request parameters. + */ +public abstract class BrowserRequestOptions extends RequestOptions { + /** + * Gets value of the client data hash. + */ + public abstract byte[] getClientDataHash(); + + public abstract Uri getOrigin(); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/COSEAlgorithmIdentifier.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/COSEAlgorithmIdentifier.java new file mode 100644 index 000000000..326d44bd7 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/COSEAlgorithmIdentifier.java @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; + +@PublicApi +public class COSEAlgorithmIdentifier implements Parcelable { + private Algorithm algorithm; + + private COSEAlgorithmIdentifier() { + } + + private COSEAlgorithmIdentifier(Algorithm algorithm) { + this.algorithm = algorithm; + } + + public static COSEAlgorithmIdentifier fromCoseValue(int value) throws UnsupportedAlgorithmIdentifierException { + if (value == RSAAlgorithm.LEGACY_RS1.getAlgoValue()) return new COSEAlgorithmIdentifier(RSAAlgorithm.RS1); + for (RSAAlgorithm algorithm : RSAAlgorithm.values()) { + if (algorithm.getAlgoValue() == value) return new COSEAlgorithmIdentifier(algorithm); + } + for (EC2Algorithm algorithm : EC2Algorithm.values()) { + if (algorithm.getAlgoValue() == value) return new COSEAlgorithmIdentifier(algorithm); + } + throw new UnsupportedAlgorithmIdentifierException(value); + } + + public int toCoseValue() { + return algorithm.getAlgoValue(); + } + + @Override + public String toString() { + return ToStringHelper.name("COSEAlgorithmIdentifier") + .value(algorithm) + .end(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(algorithm.getAlgoValue()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public COSEAlgorithmIdentifier createFromParcel(Parcel in) { + try { + return fromCoseValue(in.readInt()); + } catch (UnsupportedAlgorithmIdentifierException e) { + throw new RuntimeException(e); + } + } + + @Override + public COSEAlgorithmIdentifier[] newArray(int size) { + return new COSEAlgorithmIdentifier[size]; + } + }; + + public static class UnsupportedAlgorithmIdentifierException extends Exception { + public UnsupportedAlgorithmIdentifierException(int algId) { + super("Algorithm with COSE value " + algId + " not supported"); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationData.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationData.java new file mode 100644 index 000000000..23a06cb88 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationData.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class CableAuthenticationData extends AutoSafeParcelable { + @Field(1) + private long version; + @Field(2) + private byte[] clientEid; + @Field(3) + private byte[] authenticatorEid; + @Field(4) + private byte[] sessionPreKey; + + public static final Creator CREATOR = new AutoCreator<>(CableAuthenticationData.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationExtension.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationExtension.java new file mode 100644 index 000000000..58d85b6e5 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationExtension.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.List; + +public class CableAuthenticationExtension extends AutoSafeParcelable { + @Field(1) + private List cableAuthentication; + + public static final Creator CREATOR = new AutoCreator<>(CableAuthenticationExtension.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/EC2Algorithm.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/EC2Algorithm.java new file mode 100644 index 000000000..00fa740fc --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/EC2Algorithm.java @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; + +/** + * Algorithm names and COSE identifiers for EC2 (public) keys. + */ +@PublicApi +public enum EC2Algorithm implements Algorithm { + /** + * TPM_ECC_BN_P256 curve w/ SHA-256 + */ + ED256(-260), + /** + * ECC_BN_ISOP512 curve w/ SHA-512 + */ + ED512(-261), + /** + * ECDSA w/ SHA-256 + */ + ES256(-7), + /** + * ECDSA w/ SHA-384 + */ + ES384(-35), + /** + * ECDSA w/ SHA-512 + */ + ES512(-36); + + private final int algoValue; + + EC2Algorithm(int algoValue) { + this.algoValue = algoValue; + } + + @Override + public int getAlgoValue() { + return algoValue; + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/ErrorCode.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/ErrorCode.java new file mode 100644 index 000000000..6c5c1ec3a --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/ErrorCode.java @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +/** + * Error codes that are referenced by WebAuthn spec. + */ +@PublicApi +public enum ErrorCode implements Parcelable { + /** + * The operation is not supported. + */ + NOT_SUPPORTED_ERR(9), + /** + * The object is in an invalid state. + */ + INVALID_STATE_ERR(11), + /** + * The operation is insecure. + */ + SECURITY_ERR(18), + /** + * A network error occurred. + */ + NETWORK_ERR(19), + /** + * The operation was aborted. + */ + ABORT_ERR(20), + /** + * The operation timed out. + */ + TIMEOUT_ERR(23), + /** + * The encoding operation (either encoded or decoding) failed. + */ + ENCODING_ERR(27), + /** + * The operation failed for an unknown transient reason. + */ + UNKNOWN_ERR(28), + /** + * A mutation operation in a transaction failed because a constraint was not satisfied. + */ + CONSTRAINT_ERR(29), + /** + * Provided data is inadequate. + */ + DATA_ERR(30), + /** + * The request is not allowed by the user agent or the platform in the current context, possibly because the user + * denied permission. + */ + NOT_ALLOWED_ERR(35), + /** + * The authenticator violates the privacy requirements of the {@code AttestationStatementType} it is using. + */ + ATTESTATION_NOT_PRIVATE_ERR(36); + + private int code; + + ErrorCode(int code) { + this.code = code; + } + + @PublicApi(exclude = true) + public int getCode() { + return code; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(code); + } + + @PublicApi(exclude = true) + public static ErrorCode toErrorCode(int errorCode) throws UnsupportedErrorCodeException { + for (ErrorCode value : values()) { + if (value.code == errorCode) return value; + } + throw new UnsupportedErrorCodeException(errorCode); + } + + /** + * Exception thrown when an unsupported or unrecognized error code is encountered. + */ + public static class UnsupportedErrorCodeException extends Exception { + /** + * Constructor for the {@link ErrorCode.UnsupportedErrorCodeException}. + */ + public UnsupportedErrorCodeException(int errorCode) { + super("Error code " + errorCode + " is not supported"); + } + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new Creator() { + @Override + public ErrorCode createFromParcel(Parcel source) { + try { + return ErrorCode.toErrorCode(source.readInt()); + } catch (UnsupportedErrorCodeException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public ErrorCode[] newArray(int size) { + return new ErrorCode[size]; + } + }; +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/FidoAppIdExtension.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/FidoAppIdExtension.java new file mode 100644 index 000000000..7011404c6 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/FidoAppIdExtension.java @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Extension for FIDO appId, to support U2F backward compatibility in FIDO2 assertion requests. + *

+ * This authentication extension allows Relying Parties that have previously registered a credential using the legacy + * FIDO U2F APIs to request an assertion. Specifically, this extension allows Relying Parties to specify an appId to + * overwrite the computed rpId for U2F authenticators. + *

+ * Note that this extension is only valid if used during the get() call; other usage should result in client error. + */ +@PublicApi +public class FidoAppIdExtension extends AutoSafeParcelable { + @Field(2) + private String appId; + + public String getAppId() { + return appId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FidoAppIdExtension)) return false; + + FidoAppIdExtension that = (FidoAppIdExtension) o; + + return appId.equals(that.appId); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{appId}); + } + + public static final Creator CREATOR = new AutoCreator<>(FidoAppIdExtension.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/KeyProtectionTypes.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/KeyProtectionTypes.java new file mode 100644 index 000000000..01ad7938b --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/KeyProtectionTypes.java @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +/** + * The method used by the authenticator to protect the FIDO registration private key material. Available values are + * defined in Section 3.2 Key Protection Types. + */ +public final class KeyProtectionTypes { + /** + * This flag must be set if the authenticator uses software-based key management. Exclusive in authenticator + * metadata with KEY_PROTECTION_HARDWARE, KEY_PROTECTION_TEE, KEY_PROTECTION_SECURE_ELEMENT. + */ + public static final short KEY_PROTECTION_SOFTWARE = 1; + /** + * This flag should be set if the authenticator uses hardware-based key management. Exclusive in authenticator + * metadata with KEY_PROTECTION_SOFTWARE. + */ + public static final short KEY_PROTECTION_HARDWARE = 2; + /** + * This flag should be set if the authenticator uses the Trusted Execution Environment for key management. In + * authenticator metadata, this flag should be set in conjunction with KEY_PROTECTION_HARDWARE. Mutually exclusive + * in authenticator metadata with KEY_PROTECTION_SOFTWARE, KEY_PROTECTION_SECURE_ELEMENT. + */ + public static final short KEY_PROTECTION_TEE = 4; + /** + * This flag should be set if the authenticator uses a Secure Element for key management. In authenticator metadata, + * this flag should be set in conjunction with KEY_PROTECTION_HARDWARE. Mutually exclusive in authenticator metadata + * with KEY_PROTECTION_TEE, KEY_PROTECTION_SOFTWARE. + */ + public static final short KEY_PROTECTION_SECURE_ELEMENT = 8; + /** + * This flag must be set if the authenticator does not store (wrapped) UAuth keys at the client, but relies on a + * server-provided key handle. This flag must be set in conjunction with one of the other KEY_PROTECTION flags to + * indicate how the local key handle wrapping key and operations are protected. Servers may unset this flag in + * authenticator policy if they are not prepared to store and return key handles, for example, if they have a + * requirement to respond indistinguishably to authentication attempts against userIDs that do and do not exist. + * Refer to for more details. + */ + public static final short KEY_PROTECTION_REMOTE_HANDLE = 16; +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/MatcherProtectionTypes.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/MatcherProtectionTypes.java new file mode 100644 index 000000000..a6818f06c --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/MatcherProtectionTypes.java @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +/** + * The method used by the authenticator to protect the matcher that performs user verification. Available values are + * defined in Section 3.3 Matcher Protection Types. + */ +public final class MatcherProtectionTypes { + /** + * This flag must be set if the authenticator's matcher is running in software. Exclusive in authenticator metadata + * with MATCHER_PROTECTION_TEE, MATCHER_PROTECTION_ON_CHIP. + */ + public static final short MATCHER_PROTECTION_SOFTWARE = 1; + /** + * This flag should be set if the authenticator's matcher is running inside the Trusted Execution Environment. + * Mutually exclusive in authenticator metadata with MATCHER_PROTECTION_SOFTWARE, MATCHER_PROTECTION_ON_CHIP. + */ + public static final short MATCHER_PROTECTION_TEE = 2; + /** + * This flag should be set if the authenticator's matcher is running on the chip. Mutually exclusive in + * authenticator metadata with MATCHER_PROTECTION_TEE, MATCHER_PROTECTION_SOFTWARE. + */ + public static final short MATCHER_PROTECTION_ON_CHIP = 4; +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java new file mode 100644 index 000000000..ebc5f819a --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java @@ -0,0 +1,178 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * This class is contains the attributes that are returned to the caller when a new credential is created, or a new + * assertion is requested. + */ +@PublicApi +public class PublicKeyCredential extends AutoSafeParcelable { + @Field(1) + private String id; + @Field(2) + private String type; + @Field(3) + private byte[] rawId; + @Field(4) + private AuthenticatorAttestationResponse registerResponse; + @Field(5) + private AuthenticatorAssertionResponse signResponse; + @Field(6) + private AuthenticatorErrorResponse errorResponse; + @Field(7) + private AuthenticationExtensionsClientOutputs clientExtensionResults; + + public AuthenticationExtensionsClientOutputs getClientExtensionResults() { + return clientExtensionResults; + } + + public String getId() { + return id; + } + + public byte[] getRawId() { + return rawId; + } + + public AuthenticatorResponse getResponse() { + if (registerResponse != null) return registerResponse; + if (signResponse != null) return signResponse; + if (errorResponse != null) return errorResponse; + throw new IllegalStateException("No response set."); + } + + public String getType() { + return type; + } + + /** + * Builder for {@link PublicKeyCredential}. + */ + public static class Builder { + private String id; + private byte[] rawId; + private AuthenticatorResponse response; + private AuthenticationExtensionsClientOutputs extensionsClientOutputs; + + /** + * The constructor of {@link PublicKeyCredential.Builder}. + */ + public Builder() { + } + + /** + * Sets the output produced by the client's processing of the extensions requested by the relying party. + */ + public PublicKeyCredential.Builder setAuthenticationExtensionsClientOutputs(AuthenticationExtensionsClientOutputs extensionsClientOutputs) { + this.extensionsClientOutputs = extensionsClientOutputs; + return this; + } + + /** + * Sets the base64url encoding of the credential identifier. + */ + public Builder setId(String id) { + this.id = id; + return this; + } + + /** + * Sets the raw value of the credential identifier. + */ + public Builder setRawId(byte[] rawId) { + this.rawId = rawId; + return this; + } + + /** + * Sets the authenticator's response to the clients register or sign request. + *

+ * This attribute contains the authenticator's response to the client’s request to either create a public key + * credential, or generate an authentication assertion. If the {@link PublicKeyCredential} is created in + * response a register request, this attribute’s value will be an {@link AuthenticatorAttestationResponse}, + * otherwise, the {@link PublicKeyCredential} was created in response to a sign request, and this attribute’s + * value will be an {@link AuthenticatorAssertionResponse}. + */ + public Builder setResponse(AuthenticatorResponse response) { + this.response = response; + return this; + } + + /** + * Builds the {@link PublicKeyCredential} object. + */ + public PublicKeyCredential build() { + PublicKeyCredential credential = new PublicKeyCredential(); + credential.id = id; + credential.type = PublicKeyCredentialType.PUBLIC_KEY.toString(); + credential.rawId = rawId; + credential.clientExtensionResults = extensionsClientOutputs; + if (response instanceof AuthenticatorAttestationResponse) { + credential.registerResponse = (AuthenticatorAttestationResponse) response; + } else if (response instanceof AuthenticatorAssertionResponse) { + credential.signResponse = (AuthenticatorAssertionResponse) response; + } else if (response instanceof AuthenticatorErrorResponse) { + credential.errorResponse = (AuthenticatorErrorResponse) response; + } + return credential; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredential)) return false; + + PublicKeyCredential that = (PublicKeyCredential) o; + + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (type != null ? !type.equals(that.type) : that.type != null) return false; + if (!Arrays.equals(rawId, that.rawId)) return false; + if (registerResponse != null ? !registerResponse.equals(that.registerResponse) : that.registerResponse != null) + return false; + if (signResponse != null ? !signResponse.equals(that.signResponse) : that.signResponse != null) return false; + if (errorResponse != null ? !errorResponse.equals(that.errorResponse) : that.errorResponse != null) + return false; + return clientExtensionResults != null ? clientExtensionResults.equals(that.clientExtensionResults) : that.clientExtensionResults == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{id, type, rawId, signResponse, registerResponse, errorResponse, clientExtensionResults}); + } + + /** + * Serializes the {@link PublicKeyCredential} to bytes. Use {@link #deserializeFromBytes(byte[])} to deserialize. + * + * @return the serialized byte array. + */ + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + /** + * Deserializes the {@link PublicKeyCredential} from bytes, reversing {@link #serializeToBytes()}. + * + * @param serializedBytes The serialized bytes. + * @return The deserialized {@link PublicKeyCredential}. + */ + public static PublicKeyCredential deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredential.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java new file mode 100644 index 000000000..fbf4b5d5c --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java @@ -0,0 +1,289 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.util.Base64; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; +import java.util.List; + +/** + * This class is used to supply options when creating a new credential. + */ +@PublicApi +public class PublicKeyCredentialCreationOptions extends RequestOptions { + @Field(2) + private PublicKeyCredentialRpEntity rp; + @Field(3) + private PublicKeyCredentialUserEntity user; + @Field(4) + private byte[] challenge; + @Field(5) + private List parameters; + @Field(6) + private Double timeoutSeconds; + @Field(7) + private List excludeList; + @Field(8) + private AuthenticatorSelectionCriteria authenticatorSelection; + @Field(9) + private Integer requestId; + @Field(10) + private TokenBinding tokenBinding; + @Field(11) + private AttestationConveyancePreference attestationConveyancePreference; + @Field(12) + private AuthenticationExtensions authenticationExtensions; + + public AttestationConveyancePreference getAttestationConveyancePreference() { + return attestationConveyancePreference; + } + + public String getAttestationConveyancePreferenceAsString() { + return attestationConveyancePreference.toString(); + } + + @Override + public AuthenticationExtensions getAuthenticationExtensions() { + return authenticationExtensions; + } + + public AuthenticatorSelectionCriteria getAuthenticatorSelection() { + return authenticatorSelection; + } + + @Override + public byte[] getChallenge() { + return challenge; + } + + public List getExcludeList() { + return excludeList; + } + + public List getParameters() { + return parameters; + } + + @Override + public Integer getRequestId() { + return requestId; + } + + public PublicKeyCredentialRpEntity getRp() { + return rp; + } + + @Override + public Double getTimeoutSeconds() { + return timeoutSeconds; + } + + @Override + public TokenBinding getTokenBinding() { + return tokenBinding; + } + + public PublicKeyCredentialUserEntity getUser() { + return user; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialCreationOptions)) return false; + + PublicKeyCredentialCreationOptions that = (PublicKeyCredentialCreationOptions) o; + + if (rp != null ? !rp.equals(that.rp) : that.rp != null) return false; + if (user != null ? !user.equals(that.user) : that.user != null) return false; + if (!Arrays.equals(challenge, that.challenge)) return false; + if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null) return false; + if (timeoutSeconds != null ? !timeoutSeconds.equals(that.timeoutSeconds) : that.timeoutSeconds != null) + return false; + if (excludeList != null ? !excludeList.equals(that.excludeList) : that.excludeList != null) return false; + if (authenticatorSelection != null ? !authenticatorSelection.equals(that.authenticatorSelection) : that.authenticatorSelection != null) + return false; + if (requestId != null ? !requestId.equals(that.requestId) : that.requestId != null) return false; + if (tokenBinding != null ? !tokenBinding.equals(that.tokenBinding) : that.tokenBinding != null) return false; + if (attestationConveyancePreference != that.attestationConveyancePreference) return false; + return authenticationExtensions != null ? authenticationExtensions.equals(that.authenticationExtensions) : that.authenticationExtensions == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{rp, user, Arrays.hashCode(challenge), parameters, timeoutSeconds, excludeList, authenticatorSelection, requestId, tokenBinding, attestationConveyancePreference, authenticationExtensions}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialCreationOptions") + .field("rp", rp) + .field("user", user) + .field("challenge", challenge) + .field("parameters", parameters) + .field("timeoutSeconds", timeoutSeconds) + .field("excludeList", excludeList) + .field("authenticatorSelection", authenticatorSelection) + .field("requestId", requestId) + .field("tokenBinding", tokenBinding) + .field("attestationConveyancePreference", attestationConveyancePreference) + .field("authenticationExtensions", authenticationExtensions) + .end(); + } + + /** + * Builder for {@link PublicKeyCredentialCreationOptions}. + */ + public static class Builder { + private PublicKeyCredentialRpEntity rp; + private PublicKeyCredentialUserEntity user; + private byte[] challenge; + private List parameters; + private Double timeoutSeconds; + private List excludeList; + private AuthenticatorSelectionCriteria authenticatorSelection; + private Integer requestId; + private TokenBinding tokenBinding; + private AttestationConveyancePreference attestationConveyancePreference; + private AuthenticationExtensions authenticationExtensions; + + /** + * The constructor of {@link PublicKeyCredentialCreationOptions.Builder}. + */ + public Builder() { + } + + /** + * Sets the preference for obfuscation level of the returned attestation data. + */ + public Builder setAttestationConveyancePreference(AttestationConveyancePreference attestationConveyancePreference) { + this.attestationConveyancePreference = attestationConveyancePreference; + return this; + } + + /** + * Sets additional extensions that may dictate some client behavior during an exchange with a connected + * authenticator. + */ + public Builder setAuthenticationExtensions(AuthenticationExtensions authenticationExtensions) { + this.authenticationExtensions = authenticationExtensions; + return this; + } + + /** + * Sets constraints on the type of authenticator that is acceptable for this session. + */ + public Builder setAuthenticatorSelection(AuthenticatorSelectionCriteria authenticatorSelection) { + this.authenticatorSelection = authenticatorSelection; + return this; + } + + /** + * Sets the challenge to sign when generating the attestation for this request. + */ + public Builder setChallenge(byte[] challenge) { + this.challenge = challenge; + return this; + } + + /** + * Sets a list of credentials that, if found on a connected authenticator, will preclude registration of that + * authenticator with the relying party. This is often set to prevent re-registration of authenticators that + * the relying party has already registered on behalf of the user. + */ + public Builder setExcludeList(List excludeList) { + this.excludeList = excludeList; + return this; + } + + /** + * Sets the {@link PublicKeyCredentialParameters} that constrain the type of credential to generate. + */ + public Builder setParameters(List parameters) { + this.parameters = parameters; + return this; + } + + /** + * Sets the request id in order to link together events into a single session (the span of events between the + * time that the server initiates a single FIDO2 request to the client and receives reply) on a single device. + */ + public Builder setRequestId(Integer requestId) { + this.requestId = requestId; + return this; + } + + /** + * Sets information for a relying party, on whose behalf a given registration operation is being performed. + *

+ * Note: the RpId should be an effective domain (aka, without scheme or port); and it should also be in secure + * context (aka https connection). Apps-facing API needs to check the package signature against Digital Asset + * Links, whose resource is the RP ID with prepended "//". Privileged (browser) API doesn't need the check. + */ + public Builder setRp(PublicKeyCredentialRpEntity rp) { + this.rp = rp; + return this; + } + + /** + * Sets a timeout that limits the duration of the registration session provided to the user. + */ + public Builder setTimeoutSeconds(Double timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + return this; + } + + /** + * Sets the {@link TokenBinding} associated with the calling origin. + */ + public Builder setTokenBinding(TokenBinding tokenBinding) { + this.tokenBinding = tokenBinding; + return this; + } + + /** + * Sets information about the user on whose behalf the relying party is registering a credential. + */ + public Builder setUser(PublicKeyCredentialUserEntity user) { + this.user = user; + return this; + } + + /** + * Builds the {@link PublicKeyCredentialCreationOptions} object. + */ + public PublicKeyCredentialCreationOptions build() { + PublicKeyCredentialCreationOptions options = new PublicKeyCredentialCreationOptions(); + options.challenge = challenge; + options.timeoutSeconds = timeoutSeconds; + options.requestId = requestId; + options.tokenBinding = tokenBinding; + options.authenticationExtensions = authenticationExtensions; + return options; + } + } + + /** + * Deserializes the {@link PublicKeyCredentialCreationOptions} from bytes, reversing {@link #serializeToBytes()}. + * + * @param serializedBytes The serialized bytes. + * @return The deserialized {@link PublicKeyCredentialCreationOptions}. + */ + public static PublicKeyCredentialCreationOptions deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialCreationOptions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialDescriptor.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialDescriptor.java new file mode 100644 index 000000000..ebe67cb5d --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialDescriptor.java @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.util.Base64; + +import com.google.android.gms.fido.common.Transport; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; +import java.util.List; + +/** + * This class contains the attributes that are specified by a caller when referring to a credential as an input + * parameter to the registration or authentication method. + */ +@PublicApi +public class PublicKeyCredentialDescriptor extends AutoSafeParcelable { + @Field(2) + private PublicKeyCredentialType type; + @Field(3) + private byte[] id; + @Field(4) + private List transports; + + public byte[] getId() { + return id; + } + + public List getTransports() { + return transports; + } + + public PublicKeyCredentialType getType() { + return type; + } + + public String getTypeAsString() { + return type.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialDescriptor)) return false; + + PublicKeyCredentialDescriptor that = (PublicKeyCredentialDescriptor) o; + + if (type != that.type) return false; + if (!Arrays.equals(id, that.id)) return false; + return transports != null ? transports.equals(that.transports) : that.transports == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{type, Arrays.hashCode(id), transports}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialDescriptor") + .value(id) + .field("type", type) + .field("transports", transports) + .end(); + } + + /** + * Exception thrown when an unsupported or unrecognized public key credential descriptor is encountered. + */ + public static class UnsupportedPubKeyCredDescriptorException extends Exception { + public UnsupportedPubKeyCredDescriptorException(String message) { + super(message); + } + + public UnsupportedPubKeyCredDescriptorException(String message, Throwable cause) { + super(message, cause); + } + } + + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialDescriptor.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialParameters.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialParameters.java new file mode 100644 index 000000000..e0914c570 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialParameters.java @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * This class supplies additional parameters when creating a new credential. + */ +@PublicApi +public class PublicKeyCredentialParameters extends AutoSafeParcelable { + @Field(2) + private PublicKeyCredentialType type; + @Field(3) + private COSEAlgorithmIdentifier algorithm; + + private PublicKeyCredentialParameters() { + } + + public PublicKeyCredentialParameters(String type, int algorithm) { + try { + this.type = PublicKeyCredentialType.fromString(type); + } catch (PublicKeyCredentialType.UnsupportedPublicKeyCredTypeException e) { + throw new IllegalArgumentException(e); + } + try { + this.algorithm = COSEAlgorithmIdentifier.fromCoseValue(algorithm); + } catch (COSEAlgorithmIdentifier.UnsupportedAlgorithmIdentifierException e) { + throw new IllegalArgumentException(e); + } + } + + public COSEAlgorithmIdentifier getAlgorithm() { + return algorithm; + } + + public int getAlgorithmIdAsInteger() { + return algorithm.toCoseValue(); + } + + public PublicKeyCredentialType getType() { + return type; + } + + public String getTypeAsString() { + return type.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialParameters)) return false; + + PublicKeyCredentialParameters that = (PublicKeyCredentialParameters) o; + + if (type != that.type) return false; + return algorithm != null ? algorithm.equals(that.algorithm) : that.algorithm == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{type, algorithm}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialParameters") + .field("type", type) + .field("algorithm", algorithm) + .end(); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialParameters.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java new file mode 100644 index 000000000..0b1ab928a --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java @@ -0,0 +1,220 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; +import java.util.List; + +/** + * This class is used to supply an authentication request with the data it needs to generate an assertion. + */ +@PublicApi +public class PublicKeyCredentialRequestOptions extends RequestOptions { + @Field(2) + private byte[] challenge; + @Field(3) + private Double timeoutSeconds; + @Field(4) + private String rpId; + @Field(5) + private List allowList; + @Field(6) + private Integer requestId; + @Field(7) + private TokenBinding tokenBinding; + @Field(8) + private UserVerificationRequirement userVerificationRequirement; + @Field(9) + private AuthenticationExtensions authenticationExtensions; + + public List getAllowList() { + return allowList; + } + + @Override + public AuthenticationExtensions getAuthenticationExtensions() { + return authenticationExtensions; + } + + @Override + public byte[] getChallenge() { + return challenge; + } + + @Override + public Integer getRequestId() { + return requestId; + } + + public String getRpId() { + return rpId; + } + + @Override + public Double getTimeoutSeconds() { + return timeoutSeconds; + } + + @Override + public TokenBinding getTokenBinding() { + return tokenBinding; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialRequestOptions)) return false; + + PublicKeyCredentialRequestOptions that = (PublicKeyCredentialRequestOptions) o; + + if (!Arrays.equals(challenge, that.challenge)) return false; + if (timeoutSeconds != null ? !timeoutSeconds.equals(that.timeoutSeconds) : that.timeoutSeconds != null) + return false; + if (rpId != null ? !rpId.equals(that.rpId) : that.rpId != null) return false; + if (allowList != null ? !allowList.equals(that.allowList) : that.allowList != null) return false; + if (requestId != null ? !requestId.equals(that.requestId) : that.requestId != null) return false; + if (tokenBinding != null ? !tokenBinding.equals(that.tokenBinding) : that.tokenBinding != null) return false; + if (userVerificationRequirement != that.userVerificationRequirement) return false; + return authenticationExtensions != null ? authenticationExtensions.equals(that.authenticationExtensions) : that.authenticationExtensions == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{Arrays.hashCode(challenge), timeoutSeconds, rpId, allowList, requestId, tokenBinding, userVerificationRequirement, authenticationExtensions}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialRequestOptions") + .field("challenge", challenge) + .field("timeoutSeconds", timeoutSeconds) + .field("rpId", rpId) + .field("allowList", allowList) + .field("requestId", requestId) + .field("tokenBinding", tokenBinding) + .field("userVerificationRequirement", userVerificationRequirement) + .field("authenticationExtensions", authenticationExtensions) + .end(); + } + + /** + * Builder for {@link PublicKeyCredentialRequestOptions}. + */ + public static class Builder { + private byte[] challenge; + private Double timeoutSeconds; + private String rpId; + private List allowList; + private Integer requestId; + private TokenBinding tokenBinding; + private AuthenticationExtensions authenticationExtensions; + + /** + * The constructor of {@link PublicKeyCredentialRequestOptions.Builder}. + */ + public Builder() { + } + + /** + * Sets a list of public key credentials which constrain authentication to authenticators that contain a + * private key for at least one of the supplied public keys. + */ + public Builder setAllowList(List allowList) { + this.allowList = allowList; + return this; + } + + /** + * Sets additional extensions that may dictate some client behavior during an exchange with a connected + * authenticator. + */ + public Builder setAuthenticationExtensions(AuthenticationExtensions authenticationExtensions) { + this.authenticationExtensions = authenticationExtensions; + return this; + } + + /** + * Sets the nonce value that the authenticator should sign using a private key corresponding to a public key + * credential that is acceptable for this authentication session. + */ + public Builder setChallenge(byte[] challenge) { + this.challenge = challenge; + return this; + } + + /** + * Sets the request id in order to link together events into a single session (the span of events between the + * time that the server initiates a single FIDO2 request to the client and receives reply) on a single device. + * This field is optional. + */ + public Builder setRequestId(Integer requestId) { + this.requestId = requestId; + return this; + } + + /** + * Sets identifier for a relying party, on whose behalf a given authentication operation is being performed. + * A public key credential can only be used for authentication with the same replying party it was registered + * with. + *

+ * Note: the RpId should be an effective domain (aka, without scheme or port); and it should also be in secure + * context (aka https connection). Apps-facing API needs to check the package signature against Digital Asset + * Links, whose resource is the RP ID with prepended "//". Privileged (browser) API doesn't need the check. + */ + public Builder setRpId(String rpId) { + this.rpId = rpId; + return this; + } + + public Builder setTimeoutSeconds(Double timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + return this; + } + + /** + * Sets the {@link TokenBinding} associated with the calling origin. + */ + public Builder setTokenBinding(TokenBinding tokenBinding) { + this.tokenBinding = tokenBinding; + return this; + } + + /** + * Builds the {@link PublicKeyCredentialRequestOptions} object. + */ + public PublicKeyCredentialRequestOptions build() { + PublicKeyCredentialRequestOptions options = new PublicKeyCredentialRequestOptions(); + options.challenge = challenge; + options.timeoutSeconds = timeoutSeconds; + options.rpId = rpId; + options.allowList = allowList; + options.requestId = requestId; + options.tokenBinding = tokenBinding; + options.authenticationExtensions = authenticationExtensions; + return options; + } + } + + /** + * Deserializes the {@link PublicKeyCredentialRequestOptions} from bytes, reversing {@link #serializeToBytes()}. + * + * @param serializedBytes The serialized bytes. + * @return The deserialized {@link PublicKeyCredentialRequestOptions}. + */ + public static PublicKeyCredentialRequestOptions deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialRequestOptions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRpEntity.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRpEntity.java new file mode 100644 index 000000000..e1a980090 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRpEntity.java @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Represents the information about a relying party with which a credential is associated. + */ +@PublicApi +public class PublicKeyCredentialRpEntity extends AutoSafeParcelable { + @Field(2) + private String id; + @Field(3) + private String name; + @Field(4) + private String icon; + + private PublicKeyCredentialRpEntity() { + } + + public PublicKeyCredentialRpEntity(String id, String name, String icon) { + this.id = id; + this.name = name; + this.icon = icon; + } + + public String getIcon() { + return icon; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialRpEntity)) return false; + + PublicKeyCredentialRpEntity that = (PublicKeyCredentialRpEntity) o; + + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + return icon != null ? icon.equals(that.icon) : that.icon == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{id, name, icon}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialRpEntity") + .value(id) + .field("name", name) + .field("icon", icon) + .end(); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialRpEntity.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialType.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialType.java new file mode 100644 index 000000000..d3bd336d6 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialType.java @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +/** + * This enumeration defines the valid credential types. + */ +public enum PublicKeyCredentialType implements Parcelable { + PUBLIC_KEY("public-key"); + + private final String value; + + PublicKeyCredentialType(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static PublicKeyCredentialType fromString(String type) throws UnsupportedPublicKeyCredTypeException { + for (PublicKeyCredentialType value : values()) { + if (value.value.equals(type)) return value; + } + throw new UnsupportedPublicKeyCredTypeException("PublicKeyCredentialType " + type + " not supported"); + } + + public static Creator CREATOR = new Creator() { + @Override + public PublicKeyCredentialType createFromParcel(Parcel source) { + try { + return PublicKeyCredentialType.fromString(source.readString()); + } catch (UnsupportedPublicKeyCredTypeException e) { + throw new RuntimeException(e); + } + } + + @Override + public PublicKeyCredentialType[] newArray(int size) { + return new PublicKeyCredentialType[size]; + } + }; + + /** + * Exception thrown when an unsupported or unrecognized transport is encountered. + */ + public static class UnsupportedPublicKeyCredTypeException extends Exception { + public UnsupportedPublicKeyCredTypeException(String message) { + super(message); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialUserEntity.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialUserEntity.java new file mode 100644 index 000000000..93b88efb5 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialUserEntity.java @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * This class is used to supply additional parameters about the user account when creating a new Credential. + */ +@PublicApi +public class PublicKeyCredentialUserEntity extends AutoSafeParcelable { + @Field(2) + private byte[] id; + @Field(3) + private String name; + @Field(4) + private String icon; + @Field(5) + private String displayName; + + private PublicKeyCredentialUserEntity() { + } + + public PublicKeyCredentialUserEntity(byte[] id, String name, String icon, String displayName) { + this.id = id; + this.name = name; + this.icon = icon; + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + public String getIcon() { + return icon; + } + + public byte[] getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialUserEntity)) return false; + + PublicKeyCredentialUserEntity that = (PublicKeyCredentialUserEntity) o; + + if (!Arrays.equals(id, that.id)) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + if (icon != null ? !icon.equals(that.icon) : that.icon != null) return false; + return displayName != null ? displayName.equals(that.displayName) : that.displayName == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{id, name, icon, displayName}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialUserEntity") + .value(id) + .field("name", name) + .field("icon", icon) + .field("displayName", displayName) + .end(); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialUserEntity.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RSAAlgorithm.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RSAAlgorithm.java new file mode 100644 index 000000000..d3a9f7291 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RSAAlgorithm.java @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; + +/** + * Algorithm names and COSE identifiers for RSA (public) keys. + */ +@PublicApi +public enum RSAAlgorithm implements Algorithm{ + /** + * RSASSA-PKCS1-v1_5 w/ SHA-256 + */ + RS256(-257), + /** + * RSASSA-PKCS1-v1_5 w/ SHA-384 + */ + RS384(-258), + /** + * RSASSA-PKCS1-v1_5 w/ SHA-512 + */ + RS512(-259), + /** + * The legacy value for "RSASSA-PKCS1-v1_5 w/ SHA-1" + * @deprecated please use {@link #RS1} instead. + */ + @Deprecated + LEGACY_RS1(-262), + /** + * RSASSA-PSS w/ SHA-256 + */ + PS256(-37), + /** + * RSASSA-PSS w/ SHA-384 + */ + PS384(-38), + /** + * RSASSA-PSS w/ SHA-512 + */ + PS512(-39), + /** + * RSASSA-PKCS1-v1_5 w/ SHA-1 + */ + RS1(-65535); + + private final int algoValue; + + RSAAlgorithm(int algoValue) { + this.algoValue = algoValue; + } + + @Override + public int getAlgoValue() { + return algoValue; + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java new file mode 100644 index 000000000..f2cc361bf --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParcelUtil; + +/** + * An abstract class representing FIDO2 request options. + */ +@PublicApi +public abstract class RequestOptions extends AutoSafeParcelable { + public abstract byte[] getChallenge(); + public abstract Double getTimeoutSeconds(); + public abstract Integer getRequestId(); + public abstract TokenBinding getTokenBinding(); + public abstract AuthenticationExtensions getAuthenticationExtensions(); + + /** + * Serializes the {@link RequestOptions} to bytes. Use deserializeFromBytes(byte[]) to deserialize. + */ + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/TokenBinding.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/TokenBinding.java new file mode 100644 index 000000000..bd35485e5 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/TokenBinding.java @@ -0,0 +1,179 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.json.JSONException; +import org.json.JSONObject; +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Represents the Token binding information provided by the relying party. + */ +@PublicApi +public class TokenBinding extends AutoSafeParcelable { + /** + * A singleton instance representing that token binding is not supported by the client. + */ + public static final TokenBinding NOT_SUPPORTED = new TokenBinding(TokenBindingStatus.NOT_SUPPORTED, null); + /** + * A singleton instance representing that token binding is supported by the client, but unused by the relying party. + */ + public static final TokenBinding SUPPORTED = new TokenBinding(TokenBindingStatus.SUPPORTED, null); + + @Field(2) + private TokenBindingStatus status; + @Field(3) + private String tokenBindingId; + + private TokenBinding() { + } + + /** + * Constructs an instance of a {@link TokenBinding} for a provided token binding id. + */ + public TokenBinding(String tokenBindingId) { + status = TokenBindingStatus.PRESENT; + this.tokenBindingId = tokenBindingId; + } + + private TokenBinding(TokenBindingStatus status, String tokenBindingId) { + this.status = status; + this.tokenBindingId = tokenBindingId; + } + + /** + * Returns the token binding ID if the token binding status is {@code PRESENT}, otherwise returns null. + */ + public String getTokenBindingId() { + return tokenBindingId; + } + + /** + * Returns the stringified {@link TokenBinding.TokenBindingStatus} associated with this instance. + */ + public String getTokenBindingStatusAsString() { + return status.toString(); + } + + /** + * Returns this {@link TokenBinding} object as a {@link JSONObject}. + */ + public JSONObject toJsonObject() { + try { + return new JSONObject().put("status", this.status).put("id", this.tokenBindingId); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TokenBinding)) return false; + + TokenBinding that = (TokenBinding) o; + + if (status != that.status) return false; + return tokenBindingId != null ? tokenBindingId.equals(that.tokenBindingId) : that.tokenBindingId == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{status, tokenBindingId}); + } + + @Override + public String toString() { + return ToStringHelper.name("TokenBinding") + .value(tokenBindingId) + .field("status", status) + .end(); + } + + /** + * The token binding status specified by the client. + */ + public enum TokenBindingStatus implements Parcelable { + /** + * The client supports token binding and the relying party is using it. + */ + PRESENT("present"), + /** + * The client supports token binding but the relying party is not using it. + */ + SUPPORTED("supported"), + /** + * The client does not support token binding. + */ + NOT_SUPPORTED("not-supported"); + private String value; + + TokenBindingStatus(String value) { + this.value = value; + } + + @PublicApi(exclude = true) + public static TokenBindingStatus fromString(String str) throws UnsupportedTokenBindingStatusException { + for (TokenBindingStatus value : values()) { + if (value.value.equals(str)) return value; + } + throw new UnsupportedTokenBindingStatusException("TokenBindingStatus " + str + " not supported"); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(value); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public TokenBindingStatus createFromParcel(Parcel in) { + try { + return fromString(in.readString()); + } catch (UnsupportedTokenBindingStatusException e) { + throw new RuntimeException(e); + } + } + + @Override + public TokenBindingStatus[] newArray(int size) { + return new TokenBindingStatus[size]; + } + }; + + @Override + public String toString() { + return value; + } + } + + /** + * Exception thrown when an unsupported or unrecognized {@link TokenBinding.TokenBindingStatus} is encountered. + */ + public static class UnsupportedTokenBindingStatusException extends Exception { + public UnsupportedTokenBindingStatusException(String message) { + super(message); + } + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(TokenBinding.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethodExtension.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethodExtension.java new file mode 100644 index 000000000..d537142fd --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethodExtension.java @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Extension for FIDO User Verification Method. + *

+ * This authentication extension allows Relying Parties to ascertain the method(s) used by the user to authorize the + * operation. + *

+ * Note that this extension can be used in only sign calls. + */ +@PublicApi +public class UserVerificationMethodExtension extends AutoSafeParcelable { + @Field(1) + private boolean uvm; + + public boolean getUvm() { + return uvm; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UserVerificationMethodExtension)) return false; + + UserVerificationMethodExtension that = (UserVerificationMethodExtension) o; + + return uvm == that.uvm; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{uvm}); + } + + public static final Creator CREATOR = new AutoCreator<>(UserVerificationMethodExtension.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethods.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethods.java new file mode 100644 index 000000000..e647644a9 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethods.java @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; + +/** + * The authentication method/factor used by the authenticator to verify the user. + */ +@PublicApi +public final class UserVerificationMethods { + /** + * This flag must be set if the authenticator is able to confirm user presence in any fashion. If this flag and no + * other is set for user verification, the guarantee is only that the authenticator cannot be operated without some + * human intervention, not necessarily that the sensing of "presence" provides any level of user verification (e.g. + * a device that requires a button press to activate). + */ + public static final int USER_VERIFY_PRESENCE = 1; + /** + * This flag must be set if the authenticator uses any type of measurement of a fingerprint for user verification. + */ + public static final int USER_VERIFY_FINGERPRINT = 2; + /** + * This flag must be set if the authenticator uses a local-only passcode (i.e. a passcode not known by the server) + * for user verification. + */ + public static final int USER_VERIFY_PASSCODE = 4; + /** + * This flag must be set if the authenticator uses a voiceprint (also known as speaker recognition) for user + * verification. + */ + public static final int USER_VERIFY_VOICEPRINT = 8; + /** + * This flag must be set if the authenticator uses any manner of face recognition to verify the user. + */ + public static final int USER_VERIFY_FACEPRINT = 16; + /** + * This flag must be set if the authenticator uses any form of location sensor or measurement for user verification. + */ + public static final int USER_VERIFY_LOCATION = 32; + /** + * This flag must be set if the authenticator uses any form of eye biometrics for user verification. + */ + public static final int USER_VERIFY_EYEPRINT = 64; + /** + * This flag must be set if the authenticator uses a drawn pattern for user verification. + */ + public static final int USER_VERIFY_PATTERN = 128; + /** + * This flag must be set if the authenticator uses any measurement of a full hand (including palm-print, hand + * geometry or vein geometry) for user verification. + */ + public static final int USER_VERIFY_HANDPRINT = 256; + /** + * This flag must be set if the authenticator will respond without any user interaction (e.g. Silent Authenticator). + */ + public static final int USER_VERIFY_NONE = 512; + /** + * If an authenticator sets multiple flags for user verification types, it may also set this flag to indicate that + * all verification methods will be enforced (e.g. faceprint AND voiceprint). If flags for multiple user + * verification methods are set and this flag is not set, verification with only one is necessary (e.g. fingerprint + * OR passcode). + */ + public static final int USER_VERIFY_ALL = 1024; + + private UserVerificationMethods() { + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationRequirement.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationRequirement.java new file mode 100644 index 000000000..1bba395c9 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationRequirement.java @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +public enum UserVerificationRequirement implements Parcelable { + REQUIRED("required"), + PREFERRED("preferred"), + DISCOURAGED("discouraged"); + + private final String value; + + UserVerificationRequirement(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static UserVerificationRequirement fromString(String attachment) throws UnsupportedUserVerificationRequirementException { + for (UserVerificationRequirement value : values()) { + if (value.value.equals(attachment)) return value; + } + throw new UnsupportedUserVerificationRequirementException("User verification requirement " + attachment + " not supported"); + } + + public static Creator CREATOR = new Creator() { + @Override + public UserVerificationRequirement createFromParcel(Parcel source) { + try { + return UserVerificationRequirement.fromString(source.readString()); + } catch (UnsupportedUserVerificationRequirementException e) { + throw new RuntimeException(e); + } + } + + @Override + public UserVerificationRequirement[] newArray(int size) { + return new UserVerificationRequirement[size]; + } + }; + + public static class UnsupportedUserVerificationRequirementException extends Exception { + public UnsupportedUserVerificationRequirementException(String message) { + super(message); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntries.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntries.java new file mode 100644 index 000000000..5a6d41bf4 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntries.java @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +/** + * Represents up to three user verification methods used by the authenticator. + */ +@PublicApi +public class UvmEntries extends AutoSafeParcelable { + @Field(1) + private List uvmEntryList; + + public List getUvmEntryList() { + return uvmEntryList; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UvmEntries)) return false; + + UvmEntries that = (UvmEntries) o; + + if (uvmEntryList == null && that.uvmEntryList == null) return true; + if (uvmEntryList == null || that.uvmEntryList == null) return false; + return uvmEntryList.containsAll(that.uvmEntryList) && that.uvmEntryList.containsAll(uvmEntryList); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{new HashSet<>(uvmEntryList)}); + } + + /** + * Builder for {@link UvmEntries} + */ + public static class Builder { + private List uvmEntryList = new ArrayList<>(); + + /** + * The constructor of {@link UvmEntries.Builder}. + */ + public Builder() { + } + + public Builder addAll(List uvmEntryList) { + if (this.uvmEntryList.size() + uvmEntryList.size() > 3) throw new IllegalStateException(); + this.uvmEntryList.addAll(uvmEntryList); + return this; + } + + public Builder addUvmEntry(UvmEntry uvmEntry) { + if (uvmEntryList.size() >= 3) throw new IllegalStateException(); + uvmEntryList.add(uvmEntry); + return this; + } + + public UvmEntries build() { + UvmEntries uvmEntries = new UvmEntries(); + uvmEntries.uvmEntryList = new ArrayList<>(uvmEntryList); + return uvmEntries; + } + } + + @PublicApi(exclude = true) + public static Creator CREATOR = new AutoCreator<>(UvmEntries.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntry.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntry.java new file mode 100644 index 000000000..1f8e5d3d2 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntry.java @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Represents a single User Verification Method Entry + */ +@PublicApi +public class UvmEntry extends AutoSafeParcelable { + @Field(1) + private int userVerificationMethod; + @Field(2) + private short keyProtectionType; + @Field(3) + private short matcherProtectionType; + + public int getUserVerificationMethod() { + return userVerificationMethod; + } + + public short getKeyProtectionType() { + return keyProtectionType; + } + + public short getMatcherProtectionType() { + return matcherProtectionType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UvmEntry)) return false; + + UvmEntry uvmEntry = (UvmEntry) o; + + if (userVerificationMethod != uvmEntry.userVerificationMethod) return false; + if (keyProtectionType != uvmEntry.keyProtectionType) return false; + return matcherProtectionType == uvmEntry.matcherProtectionType; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{userVerificationMethod, keyProtectionType, matcherProtectionType}); + } + + /** + * Builder for {@link UvmEntry}. + */ + public static class Builder { + private int userVerificationMethod; + private short keyProtectionType; + private short matcherProtectionType; + + public Builder setUserVerificationMethod(int userVerificationMethod) { + this.userVerificationMethod = userVerificationMethod; + return this; + } + + public Builder setKeyProtectionType(short keyProtectionType) { + this.keyProtectionType = keyProtectionType; + return this; + } + + public Builder setMatcherProtectionType(short matcherProtectionType) { + this.matcherProtectionType = matcherProtectionType; + return this; + } + + public UvmEntry build() { + UvmEntry entry = new UvmEntry(); + entry.userVerificationMethod = userVerificationMethod; + entry.keyProtectionType = keyProtectionType; + entry.matcherProtectionType = matcherProtectionType; + return entry; + } + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(UvmEntry.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceDirectTransferResult.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceDirectTransferResult.java new file mode 100644 index 000000000..4ab8c58a4 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceDirectTransferResult.java @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.sourcedevice; + +import android.app.Activity; +import android.content.Intent; + +import com.google.android.gms.common.api.Status; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +/** + * Result returned from the UI activity in {@link Activity#onActivityResult(int, int, Intent)} after the direct transfer finishes. + */ +@PublicApi +public class SourceDirectTransferResult extends AutoSafeParcelable { + @Field(1) + private Status status; + + private SourceDirectTransferResult() { + } + + public SourceDirectTransferResult(Status status) { + this.status = status; + } + + /** + * Gets the {@link Status} from the returned {@link SourceDirectTransferResult}. + */ + public Status getStatus() { + return status; + } + + public static final Creator CREATOR = new AutoCreator<>(SourceDirectTransferResult.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceStartDirectTransferOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceStartDirectTransferOptions.java new file mode 100644 index 000000000..50aaf81ec --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceStartDirectTransferOptions.java @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.sourcedevice; + +import org.microg.safeparcel.AutoSafeParcelable; + +/** + * Customized options to start direct transfer. + */ +public class SourceStartDirectTransferOptions extends AutoSafeParcelable { + /** + * Value of the callerType if the caller is unknown. + */ + public static final int CALLER_TYPE_UNKNOWN = 0; + /** + * Value of the callerType if the caller is browser. + */ + public static final int CALLER_TYPE_BROWSER = 2; + + @Field(1) + private int callerType; + + private SourceStartDirectTransferOptions() { + } + + /** + * Constructor for the {@link SourceStartDirectTransferOptions}. + */ + public SourceStartDirectTransferOptions(int callerType) { + this.callerType = callerType; + } + + public static final Creator CREATOR = new AutoCreator<>(SourceStartDirectTransferOptions.class); +} diff --git a/play-services-fido-api/src/main/java/org/microg/gms/fido/api/FidoConstants.java b/play-services-fido-api/src/main/java/org/microg/gms/fido/api/FidoConstants.java new file mode 100644 index 000000000..c03055823 --- /dev/null +++ b/play-services-fido-api/src/main/java/org/microg/gms/fido/api/FidoConstants.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.api; + +public class FidoConstants { + public static final String FIDO2_KEY_CREDENTIAL_EXTRA = "FIDO2_CREDENTIAL_EXTRA"; + public static final String FIDO2_KEY_ERROR_EXTRA = "FIDO2_ERROR_EXTRA"; + public static final String FIDO2_KEY_RESPONSE_EXTRA = "FIDO2_RESPONSE_EXTRA"; + public static final String KEY_RESPONSE_EXTRA = "RESPONSE_EXTRA"; +} diff --git a/play-services-fido-core/build.gradle b/play-services-fido-core/build.gradle new file mode 100644 index 000000000..4172f9e31 --- /dev/null +++ b/play-services-fido-core/build.gradle @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-fido-api') + + implementation project(':play-services-base-core') + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" + + implementation "androidx.appcompat:appcompat:$appcompatVersion" + implementation "androidx.biometric:biometric:$biometricVersion" + implementation "androidx.core:core-ktx:$coreVersion" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" + + // Navigation + implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" + implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" + + implementation 'com.upokecenter:cbor:4.5.2' +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + buildFeatures { + dataBinding = true + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-fido' diff --git a/play-services-fido-core/src/main/AndroidManifest.xml b/play-services-fido-core/src/main/AndroidManifest.xml new file mode 100644 index 000000000..eb25e441c --- /dev/null +++ b/play-services-fido-core/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/InternalCredentialStore.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/InternalCredentialStore.kt new file mode 100644 index 000000000..5f0d04a20 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/InternalCredentialStore.kt @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import android.util.Log +import androidx.annotation.RequiresApi +import org.microg.gms.utils.toBase64 +import java.security.* +import java.security.spec.ECGenParameterSpec +import kotlin.random.Random + +const val TAG = "FidoApi" + +@RequiresApi(23) +class InternalCredentialStore(val context: Context) { + private val keyStore by lazy { KeyStore.getInstance("AndroidKeyStore").apply { load(null) } } + + private fun getAlias(rpId: String, keyId: ByteArray): String = + "1." + keyId.toBase64(Base64.NO_PADDING, Base64.NO_WRAP) + "." + rpId + + private fun getPrivateKey(rpId: String, keyId: ByteArray) = keyStore.getKey(getAlias(rpId, keyId), null) as? PrivateKey + + fun createKey(rpId: String): ByteArray { + val keyId = Random.nextBytes(32) + val identifier = getAlias(rpId, keyId) + Log.d(TAG, "Creating key for $identifier") + val generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore") + generator.initialize( + KeyGenParameterSpec.Builder(identifier, KeyProperties.PURPOSE_SIGN) + .setDigests(KeyProperties.DIGEST_SHA256) + .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) + .setUserAuthenticationRequired(true) + .build() + ) + generator.generateKeyPair() + return keyId + } + + fun getPublicKey(rpId: String, keyId: ByteArray): PublicKey? = + keyStore.getCertificate(getAlias(rpId, keyId))?.publicKey + + fun getSignature(rpId: String, keyId: ByteArray): Signature? { + val privateKey = getPrivateKey(rpId, keyId) ?: return null + val signature = Signature.getInstance("SHA256withECDSA") + signature.initSign(privateKey) + return signature + } + + fun containsKey(rpId: String, keyId: ByteArray): Boolean = keyStore.containsAlias(getAlias(rpId, keyId)) +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt new file mode 100644 index 000000000..0f579195e --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt @@ -0,0 +1,434 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core + +import android.annotation.TargetApi +import android.content.Context +import android.util.Base64 +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricPrompt +import androidx.fragment.app.FragmentActivity +import com.google.android.gms.fido.fido2.api.common.* +import com.google.android.gms.fido.fido2.api.common.ErrorCode.* +import com.upokecenter.cbor.CBORObject +import kotlinx.coroutines.suspendCancellableCoroutine +import org.json.JSONObject +import org.microg.gms.fido.core.RequestOptionsType.REGISTER +import org.microg.gms.fido.core.RequestOptionsType.SIGN +import org.microg.gms.utils.getApplicationLabel +import org.microg.gms.utils.getFirstSignatureDigest +import org.microg.gms.utils.toBase64 +import java.math.BigInteger +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.security.MessageDigest +import java.security.PublicKey +import java.security.Signature +import java.security.interfaces.ECPublicKey +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.experimental.or + +class RequestHandlingException(val errorCode: ErrorCode, message: String? = null) : Exception(message) + +enum class RequestOptionsType { REGISTER, SIGN } + +val RequestOptions.registerOptions: PublicKeyCredentialCreationOptions + get() = when (this) { + is BrowserPublicKeyCredentialCreationOptions -> publicKeyCredentialCreationOptions + is PublicKeyCredentialCreationOptions -> this + else -> throw RequestHandlingException(DATA_ERR, "The request options are not valid") + } + +val RequestOptions.signOptions: PublicKeyCredentialRequestOptions + get() = when (this) { + is BrowserPublicKeyCredentialRequestOptions -> publicKeyCredentialRequestOptions + is PublicKeyCredentialRequestOptions -> this + else -> throw RequestHandlingException(DATA_ERR, "The request options are not valid") + } + +val RequestOptions.type: RequestOptionsType + get() = when (this) { + is PublicKeyCredentialCreationOptions, is BrowserPublicKeyCredentialCreationOptions -> REGISTER + is PublicKeyCredentialRequestOptions, is BrowserPublicKeyCredentialRequestOptions -> RequestOptionsType.SIGN + else -> throw RequestHandlingException(INVALID_STATE_ERR) + } + +val RequestOptions.webAuthnType: String + get() = when (type) { + REGISTER -> "webauthn.create" + SIGN -> "webauthn.get" + } + +val RequestOptions.challenge: ByteArray + get() = when (type) { + REGISTER -> registerOptions.challenge + SIGN -> signOptions.challenge + } + +val RequestOptions.rpId: String + get() = when (type) { + REGISTER -> registerOptions.rp.id + SIGN -> signOptions.rpId + } + +fun RequestOptions.checkIsValid(context: Context, callingPackage: String) { + if (type == REGISTER) { + if (registerOptions.authenticatorSelection.requireResidentKey == true) { + throw RequestHandlingException( + NOT_SUPPORTED_ERR, + "Resident credentials or empty 'allowCredentials' lists are not supported at this time." + ) + } + } + if (type == SIGN) { + if (signOptions.allowList.isNullOrEmpty()) { + throw RequestHandlingException(NOT_ALLOWED_ERR, "Request doesn't have a valid list of allowed credentials.") + } + } + if (this is BrowserRequestOptions) { + // TODO: Check properly if package is allowed to act as browser + if (callingPackage != "com.android.chrome") { + throw RequestHandlingException(NOT_ALLOWED_ERR, "Not a browser.") + } + } +} + +fun RequestOptions.getWebAuthnClientData(callingPackage: String, origin: String? = null): ByteArray { + val obj = JSONObject() + .put("type", webAuthnType) + .put("challenge", challenge.toBase64(Base64.NO_PADDING, Base64.NO_WRAP, Base64.URL_SAFE)) + .put("androidPackageName", callingPackage) + .put("tokenBinding", tokenBinding?.toJsonObject()) + if (origin != null) { + obj.put("origin", origin) + } else if (this is BrowserRequestOptions) { + obj.put("origin", this.origin.toString()) + } + return obj.toString().encodeToByteArray() +} + +fun getApplicationName(context: Context, options: RequestOptions, callingPackage: String): String = when (options) { + is BrowserPublicKeyCredentialCreationOptions, is BrowserPublicKeyCredentialRequestOptions -> options.rpId + else -> context.packageManager.getApplicationLabel(callingPackage).toString() +} + +fun getFacetId(context: Context, options: RequestOptions, callingPackage: String): String = when { + options is BrowserRequestOptions -> { + if (options.origin.scheme == null || options.origin.authority == null) { + throw RequestHandlingException(NOT_ALLOWED_ERR, "Bad url ${options.origin}") + } + "${options.origin.scheme}://${options.origin.authority}" + } + else -> { + val digest = context.packageManager.getFirstSignatureDigest(callingPackage, "SHA-256") + ?: throw RequestHandlingException(NOT_ALLOWED_ERR, "Unknown package $callingPackage") + "android:apk-key-hash:${digest.toBase64(Base64.NO_PADDING, Base64.NO_WRAP, Base64.URL_SAFE)}" + } +} + +class AttestedCredentialData(val aaguid: ByteArray, val id: ByteArray, val publicKey: ByteArray) { + fun encode() = ByteBuffer.allocate(aaguid.size + 2 + id.size + publicKey.size) + .put(aaguid) + .order(ByteOrder.BIG_ENDIAN).putShort(id.size.toShort()) + .put(id) + .put(publicKey) + .array() +} + + +class AuthenticatorData( + val rpIdHash: ByteArray, + val userPresent: Boolean, + val userVerified: Boolean, + val signCount: Int, + val attestedCredentialData: AttestedCredentialData? = null, + val extensions: ByteArray? = null +) { + fun encode(): ByteArray { + val attestedCredentialData = attestedCredentialData?.encode() ?: ByteArray(0) + val extensions = extensions ?: ByteArray(0) + return ByteBuffer.allocate(rpIdHash.size + 5 + attestedCredentialData.size + extensions.size) + .put(rpIdHash) + .put(buildFlags(userPresent, userVerified, attestedCredentialData.isNotEmpty(), extensions.isNotEmpty())) + .order(ByteOrder.BIG_ENDIAN).putInt(signCount) + .put(attestedCredentialData) + .put(extensions) + .array() + } + + fun toCBOR(): CBORObject = encode().toCBOR() + + companion object { + /** User Present **/ + private const val FLAG_UP: Byte = 1 + + /** User Verified **/ + private const val FLAG_UV: Byte = 4 + + /** Attested credential data included **/ + private const val FLAG_AT: Byte = 64 + + /** Extension data included **/ + private const val FLAG_ED: Byte = -128 + + private fun buildFlags(up: Boolean, uv: Boolean, at: Boolean, ed: Boolean): Byte = + (if (up) FLAG_UP else 0) or (if (uv) FLAG_UV else 0) or (if (at) FLAG_AT else 0) or (if (ed) FLAG_ED else 0) + } +} + +fun String.toCBOR() = CBORObject.FromObject(this) +fun ByteArray.toCBOR() = CBORObject.FromObject(this) +fun Int.toCBOR() = CBORObject.FromObject(this) + +abstract class AttestationObject(val authData: AuthenticatorData) { + abstract val fmt: String + abstract val attStmt: CBORObject + + fun encode(): ByteArray = CBORObject.NewMap().apply { + set("fmt", fmt.toCBOR()) + set("attStmt", attStmt) + set("authData", authData.toCBOR()) + }.EncodeToBytes() +} + +class NoneAttestationObject(authData: AuthenticatorData) : AttestationObject(authData) { + override val fmt: String + get() = "none" + override val attStmt: CBORObject + get() = CBORObject.NewMap() +} + +class AndroidSafetyNetAttestationObject(authData: AuthenticatorData, val ver: String, val response: ByteArray) : + AttestationObject(authData) { + override val fmt: String + get() = "android-safetynet" + override val attStmt: CBORObject + get() = CBORObject.NewMap().apply { + set("ver", ver.toCBOR()) + set("response", response.toCBOR()) + } +} + +class CoseKey( + val algorithm: Algorithm, + val x: BigInteger, + val y: BigInteger, + val curveId: Int, + val curvePointSize: Int +) { + fun encode(): ByteArray = CBORObject.NewMap().apply { + set(1, 2.toCBOR()) + set(3, algorithm.algoValue.toCBOR()) + set(-1, curveId.toCBOR()) + set(-2, x.toByteArray(curvePointSize).toCBOR()) + set(-3, y.toByteArray(curvePointSize).toCBOR()) + }.EncodeToBytes() + + companion object { + fun BigInteger.toByteArray(size: Int): ByteArray { + val res = ByteArray(size) + val orig = toByteArray() + if (orig.size > size) { + System.arraycopy(orig, orig.size - size, res, 0, size) + } else { + System.arraycopy(orig, 0, res, size - orig.size, orig.size) + } + return res + } + } +} + +class CredentialId(val type: Byte, val data: ByteArray, val rpId: String, val publicKey: PublicKey) { + fun encode(): ByteArray = ByteBuffer.allocate(1 + data.size + 32).apply { + put(type) + put(data) + put((rpId.toByteArray() + publicKey.encoded).digest("SHA-256")) + }.array() + + companion object { + fun decodeTypeAndData(bytes: ByteArray): Pair { + val buffer = ByteBuffer.wrap(bytes) + val type = buffer.get() + val data = ByteArray(32) + buffer.get(data) + return type to data + } + } +} + +fun ByteArray.digest(md: String): ByteArray = MessageDigest.getInstance(md).digest(this) + +fun getClientDataAndHash(options: RequestOptions, callingPackage: String): Pair { + val clientData: ByteArray? + var clientDataHash = (options as? BrowserPublicKeyCredentialCreationOptions)?.clientDataHash + if (clientDataHash == null) { + clientData = options.getWebAuthnClientData(callingPackage) + clientDataHash = clientData.digest("SHA-256") + } else { + clientData = "".toByteArray() + } + return clientData to clientDataHash +} + +@TargetApi(23) +suspend fun getActiveSignature( + activity: FragmentActivity, + options: RequestOptions, + callingPackage: String, + store: InternalCredentialStore, + keyId: ByteArray +): Signature { + val signature = store.getSignature(options.rpId, keyId) ?: throw RequestHandlingException(INVALID_STATE_ERR) + suspendCancellableCoroutine { continuation -> + val prompt = BiometricPrompt(activity, object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + continuation.resume(result) + } + + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + val errorMessage = when (errorCode) { + BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED, BiometricPrompt.ERROR_NEGATIVE_BUTTON -> "User canceled verification" + else -> errString.toString() + } + continuation.resumeWithException(RequestHandlingException(NOT_ALLOWED_ERR, errorMessage)) + } + }) + prompt.authenticate( + BiometricPrompt.PromptInfo.Builder() + .setTitle(activity.getString(R.string.fido_biometric_prompt_title)) + .setDescription( + activity.getString( + R.string.fido_biometric_prompt_body, + getApplicationName(activity, options, callingPackage) + ) + ) + .setNegativeButtonText(activity.getString(android.R.string.cancel)) + .build(), + BiometricPrompt.CryptoObject(signature) + ) + continuation.invokeOnCancellation { prompt.cancelAuthentication() } + } + return signature +} + +@RequiresApi(23) +suspend fun registerInternal( + activity: FragmentActivity, + options: RequestOptions, + callingPackage: String +): AuthenticatorAttestationResponse { + if (options.type != REGISTER) throw RequestHandlingException(INVALID_STATE_ERR) + val store = InternalCredentialStore(activity) + // TODO: Privacy? + for (descriptor in options.registerOptions.excludeList.orEmpty()) { + if (store.containsKey(options.rpId, descriptor.id)) { + throw RequestHandlingException( + NOT_ALLOWED_ERR, + "An excluded credential has already been registered with the device" + ) + } + } + val (clientData, clientDataHash) = getClientDataAndHash(options, callingPackage) + if (options.registerOptions.attestationConveyancePreference in setOf(AttestationConveyancePreference.NONE, null)) { + // No attestation needed + } else { + // TODO: SafetyNet + throw RequestHandlingException(NOT_SUPPORTED_ERR, "SafetyNet Attestation not yet supported") + } + val keyId = store.createKey(options.rpId) + val publicKey = store.getPublicKey(options.rpId, keyId) ?: throw RequestHandlingException(INVALID_STATE_ERR) + + // We're ignoring the signature object as we don't need it for registration + getActiveSignature(activity, options, callingPackage, store, keyId) + + val (x, y) = (publicKey as ECPublicKey).w.let { it.affineX to it.affineY } + val coseKey = CoseKey(EC2Algorithm.ES256, x, y, 1, 32) + val credentialId = CredentialId(1, keyId, options.rpId, publicKey) + + val credentialData = AttestedCredentialData( + ByteArray(16), // 0xb93fd961f2e6462fb12282002247de78 for SafetyNet + credentialId.encode(), + coseKey.encode() + ) + + val authenticatorData = AuthenticatorData( + options.rpId.toByteArray().digest("SHA-256"), + userPresent = true, + userVerified = true, + signCount = 0, + attestedCredentialData = credentialData + ) + + return AuthenticatorAttestationResponse( + credentialId.encode(), + clientData, + NoneAttestationObject(authenticatorData).encode() + ) +} + +@RequiresApi(23) +suspend fun signInternal( + activity: FragmentActivity, + options: RequestOptions, + callingPackage: String +): AuthenticatorAssertionResponse { + if (options.type != SIGN) throw RequestHandlingException(INVALID_STATE_ERR) + val store = InternalCredentialStore(activity) + val candidates = mutableListOf() + for (descriptor in options.signOptions.allowList) { + try { + val (type, data) = CredentialId.decodeTypeAndData(descriptor.id) + if (type == 1.toByte() && store.containsKey(options.rpId, data)) { + candidates.add(CredentialId(type, data, options.rpId, store.getPublicKey(options.rpId, data)!!)) + } + } catch (e: Exception) { + // Not in store or unknown id + } + } + if (candidates.isEmpty()) { + // TODO: Privacy + throw RequestHandlingException( + NOT_ALLOWED_ERR, + "Cannot find credential in local KeyStore or database" + ) + } + + val (clientData, clientDataHash) = getClientDataAndHash(options, callingPackage) + + val credentialId = candidates.first() + val keyId = credentialId.data + + val (x, y) = (credentialId.publicKey as ECPublicKey).w.let { it.affineX to it.affineY } + val coseKey = CoseKey(EC2Algorithm.ES256, x, y, 1, 32) + + val credentialData = AttestedCredentialData( + ByteArray(16), // 0xb93fd961f2e6462fb12282002247de78 for SafetyNet + credentialId.encode(), + coseKey.encode() + ) + + val authenticatorData = AuthenticatorData( + options.rpId.toByteArray().digest("SHA-256"), + userPresent = true, + userVerified = true, + signCount = 0, + attestedCredentialData = credentialData + ) + + val signature = getActiveSignature(activity, options, callingPackage, store, keyId) + + signature.update(authenticatorData.encode() + clientDataHash) + val sig = signature.sign() + + return AuthenticatorAssertionResponse( + credentialId.encode(), + clientData, + authenticatorData.encode(), + sig, + null + ) +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/privileged/Fido2PrivilegedService.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/privileged/Fido2PrivilegedService.kt new file mode 100644 index 000000000..eeb3e049d --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/privileged/Fido2PrivilegedService.kt @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.privileged + +import android.app.KeyguardManager +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_IMMUTABLE +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import android.content.Context +import android.content.Context.KEYGUARD_SERVICE +import android.content.Intent +import android.os.Build +import android.os.Parcel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.fido.fido2.api.IBooleanCallback +import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialCreationOptions +import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialRequestOptions +import com.google.android.gms.fido.fido2.internal.privileged.IFido2PrivilegedCallbacks +import com.google.android.gms.fido.fido2.internal.privileged.IFido2PrivilegedService +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.common.GmsService.FIDO2_PRIVILEGED +import org.microg.gms.fido.core.ui.AuthenticatorActivity +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.SOURCE_BROWSER +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_SOURCE +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_OPTIONS +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_SERVICE +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_TYPE +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.TYPE_REGISTER +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.TYPE_SIGN +import org.microg.gms.utils.warnOnTransactionIssues + +const val TAG = "Fido2Privileged" + +class Fido2PrivilegedService : BaseService(TAG, FIDO2_PRIVILEGED) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + callback.onPostInitComplete( + CommonStatusCodes.SUCCESS, + Fido2PrivilegedServiceImpl(this, lifecycle).asBinder(), + null + ); + } +} + +class Fido2PrivilegedServiceImpl(private val context: Context, private val lifecycle: Lifecycle) : + IFido2PrivilegedService.Stub(), LifecycleOwner { + override fun register(callbacks: IFido2PrivilegedCallbacks, options: BrowserPublicKeyCredentialCreationOptions) { + lifecycleScope.launchWhenStarted { + val intent = Intent(context, AuthenticatorActivity::class.java) + .putExtra(KEY_SERVICE, FIDO2_PRIVILEGED.SERVICE_ID) + .putExtra(KEY_SOURCE, SOURCE_BROWSER) + .putExtra(KEY_TYPE, TYPE_REGISTER) + .putExtra(KEY_OPTIONS, options.serializeToBytes()) + + val pendingIntent = + PendingIntent.getActivity(context, options.hashCode(), intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) + callbacks.onPendingIntent(Status.SUCCESS, pendingIntent) + } + } + + override fun sign(callbacks: IFido2PrivilegedCallbacks, options: BrowserPublicKeyCredentialRequestOptions) { + lifecycleScope.launchWhenStarted { + val intent = Intent(context, AuthenticatorActivity::class.java) + .putExtra(KEY_SERVICE, FIDO2_PRIVILEGED.SERVICE_ID) + .putExtra(KEY_SOURCE, SOURCE_BROWSER) + .putExtra(KEY_TYPE, TYPE_SIGN) + .putExtra(KEY_OPTIONS, options.serializeToBytes()) + + val pendingIntent = + PendingIntent.getActivity(context, options.hashCode(), intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) + callbacks.onPendingIntent(Status.SUCCESS, pendingIntent) + } + } + + override fun isUserVerifyingPlatformAuthenticatorAvailable(callbacks: IBooleanCallback) { + lifecycleScope.launchWhenStarted { + if (Build.VERSION.SDK_INT < 24) { + callbacks.onBoolean(false) + } else { + val keyguardManager = context.getSystemService(KEYGUARD_SERVICE) as? KeyguardManager? + callbacks.onBoolean(keyguardManager?.isDeviceSecure == true) + } + } + } + + override fun getLifecycle(): Lifecycle = lifecycle + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt new file mode 100644 index 000000000..2b67e6b75 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.annotation.TargetApi +import android.app.KeyguardManager +import android.bluetooth.BluetoothManager +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.nfc.NfcAdapter +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.getSystemService +import androidx.fragment.app.commit +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.NavHostFragment +import com.google.android.gms.fido.fido2.api.common.* +import com.google.android.gms.fido.fido2.api.common.ErrorCode.* +import org.microg.gms.common.GmsService +import org.microg.gms.fido.api.FidoConstants.* +import org.microg.gms.fido.core.* +import org.microg.gms.fido.core.ui.AuthenticatorActivityFragmentData.Companion.KEY_APP_NAME +import org.microg.gms.fido.core.ui.AuthenticatorActivityFragmentData.Companion.KEY_FACET_ID +import org.microg.gms.fido.core.ui.AuthenticatorActivityFragmentData.Companion.KEY_IS_FIRST +import org.microg.gms.fido.core.ui.AuthenticatorActivityFragmentData.Companion.KEY_SUPPORTED_TRANSPORTS +import org.microg.gms.fido.core.ui.Transport.* + +const val TAG = "FidoUi" + +class AuthenticatorActivity : AppCompatActivity() { + private val service: GmsService + get() = GmsService.byServiceId(intent.getIntExtra(KEY_SERVICE, GmsService.UNKNOWN.SERVICE_ID)) + val options: RequestOptions? + get() = when (intent.getStringExtra(KEY_SOURCE) to intent.getStringExtra(KEY_TYPE)) { + SOURCE_BROWSER to TYPE_REGISTER -> + BrowserPublicKeyCredentialCreationOptions.deserializeFromBytes(intent.getByteArrayExtra(KEY_OPTIONS)) + SOURCE_BROWSER to TYPE_SIGN -> + BrowserPublicKeyCredentialRequestOptions.deserializeFromBytes(intent.getByteArrayExtra(KEY_OPTIONS)) + SOURCE_APP to TYPE_REGISTER -> + PublicKeyCredentialCreationOptions.deserializeFromBytes(intent.getByteArrayExtra(KEY_OPTIONS)) + SOURCE_APP to TYPE_SIGN -> + PublicKeyCredentialRequestOptions.deserializeFromBytes(intent.getByteArrayExtra(KEY_OPTIONS)) + else -> null + } + + private lateinit var navHostFragment: NavHostFragment + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + try { + + val callingPackage = callingActivity?.packageName + if (callingPackage == null) { + return finish() + } else if (!intent.extras?.keySet().orEmpty().containsAll(REQUIRED_EXTRAS)) { + return finish(UNKNOWN_ERR, "Extra missing from request") + } else if (Build.VERSION.SDK_INT < 24) { + return finish(NOT_SUPPORTED_ERR, "FIDO2 API is not supported on devices below N") + } + val options = options ?: return finish(DATA_ERR, "The request options are not valid") + + Log.d(TAG, "onCreate caller=$callingPackage options=$options") + + options.checkIsValid(this, callingPackage) + val facetId = getFacetId(this, options, callingPackage) + val appName = getApplicationName(this, options, callingPackage) + + Log.d(TAG, "facetId=$facetId, appName=$appName") + + if (options.type == RequestOptionsType.SIGN) { + val store = InternalCredentialStore(this) + for (descriptor in options.signOptions.allowList) { + try { + val (type, data) = CredentialId.decodeTypeAndData(descriptor.id) + if (type == 1.toByte() && store.containsKey(options.rpId, data)) { + startScreenLockHandling() + window.setBackgroundDrawable(ColorDrawable(0)) + window.statusBarColor = Color.TRANSPARENT + setTheme(R.style.Theme_Fido_Translucent) + return + } + } catch (e: Exception) { + // Ignore + } + } + } + + setTheme(R.style.Theme_AppCompat_DayNight_NoActionBar) + setContentView(R.layout.fido_authenticator_activity) + val arguments = Bundle().apply { + putString(KEY_APP_NAME, appName) + putString(KEY_FACET_ID, facetId) + putBoolean(KEY_IS_FIRST, true) + putStringArrayList(KEY_SUPPORTED_TRANSPORTS, + ArrayList(listOfNotNull( + BLUETOOTH.takeIf { getSystemService()?.adapter != null }, + NFC.takeIf { NfcAdapter.getDefaultAdapter(this@AuthenticatorActivity) != null }, + USB.takeIf { packageManager.hasSystemFeature("android.hardware.usb.host") }, + SCREEN_LOCK.takeIf { Build.VERSION.SDK_INT >= 23 && getSystemService()?.isDeviceSecure == true } + ).filter { it in IMPLEMENTED_TRANSPORTS }.map { it.toString() }) + ) + } + navHostFragment = NavHostFragment.create(R.navigation.nav_fido_authenticator, arguments) + // TODO: Go directly to appropriate fragment for known key + // TODO: If not first usage, skip welcome and go directly to transport selection + //navHostFragment.findNavController().navigate(next, arguments) + supportFragmentManager.commit { + replace(R.id.fragment_container, navHostFragment) + } + } catch (e: RequestHandlingException) { + finish(e.errorCode, e.message ?: e.errorCode.name) + } catch (e: Exception) { + finish(UNKNOWN_ERR, e.message ?: e.javaClass.simpleName) + } + } + + fun finish(errorCode: ErrorCode, errorMessage: String) { + Log.d(TAG, "Finish with error: $errorMessage ($errorCode)") + finish(AuthenticatorErrorResponse(errorCode, errorMessage)) + } + + fun finish(response: AuthenticatorResponse) { + Log.d(TAG, "Finish with response: $response") + finish(PublicKeyCredential.Builder().setResponse(response).build()) + } + + fun finish(publicKeyCredential: PublicKeyCredential) { + val intent = Intent() + intent.putExtra(FIDO2_KEY_CREDENTIAL_EXTRA, publicKeyCredential.serializeToBytes()) + val response: AuthenticatorResponse = publicKeyCredential.response + if (response is AuthenticatorErrorResponse) { + intent.putExtra(FIDO2_KEY_ERROR_EXTRA, response.serializeToBytes()) + } else { + intent.putExtra(FIDO2_KEY_RESPONSE_EXTRA, response.serializeToBytes()) + } + setResult(-1, intent) + finish() + } + + @TargetApi(23) + fun startScreenLockHandling() { + lifecycleScope.launchWhenStarted { + val options = options ?: return@launchWhenStarted + val callingPackage = callingPackage ?: return@launchWhenStarted + try { + val result = when (options.type) { + RequestOptionsType.REGISTER -> registerInternal(this@AuthenticatorActivity, options, callingPackage) + RequestOptionsType.SIGN -> signInternal(this@AuthenticatorActivity, options, callingPackage) + } + finish(result) + } catch (e: RequestHandlingException) { + Log.w(TAG, e) + finish(e.errorCode, e.message ?: e.errorCode.name) + } catch (e: Exception) { + Log.w(TAG, e) + finish(ErrorCode.UNKNOWN_ERR, e.message ?: e.javaClass.simpleName) + } + } + } + + companion object { + const val KEY_SERVICE = "service" + const val KEY_SOURCE = "source" + const val KEY_TYPE = "type" + const val KEY_OPTIONS = "options" + val REQUIRED_EXTRAS = setOf(KEY_SERVICE, KEY_SOURCE, KEY_TYPE, KEY_OPTIONS) + + const val SOURCE_BROWSER = "browser" + const val SOURCE_APP = "app" + + const val TYPE_REGISTER = "register" + const val TYPE_SIGN = "sign" + + val IMPLEMENTED_TRANSPORTS = setOf(USB, SCREEN_LOCK) + } +} + + diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragment.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragment.kt new file mode 100644 index 000000000..10583e00f --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragment.kt @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.annotation.TargetApi +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.fido.fido2.api.common.ErrorCode +import org.microg.gms.fido.core.* + +@TargetApi(24) +abstract class AuthenticatorActivityFragment : Fragment() { + val data: AuthenticatorActivityFragmentData + get() = AuthenticatorActivityFragmentData(arguments ?: Bundle.EMPTY) + val authenticatorActivity: AuthenticatorActivity? + get() = activity as? AuthenticatorActivity + + fun startScreenLockHandling() = authenticatorActivity?.startScreenLockHandling() + + abstract override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragmentData.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragmentData.kt new file mode 100644 index 000000000..612cae4db --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragmentData.kt @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.os.Bundle +import org.microg.gms.fido.core.ui.AuthenticatorActivityFragmentData.Companion.KEY_IS_FIRST + +class AuthenticatorActivityFragmentData(val arguments: Bundle) { + val appName: String? + get() = arguments.getString(KEY_APP_NAME) + + val facetId: String? + get() = arguments.getString(KEY_FACET_ID) + + val isFirst: Boolean + get() = arguments.getBoolean(KEY_IS_FIRST) ?: true + + val supportedTransports: Set + get() = arguments.getStringArrayList(KEY_SUPPORTED_TRANSPORTS)?.map { Transport.valueOf(it) }?.toSet().orEmpty() + + companion object { + const val KEY_APP_NAME = "appName" + const val KEY_FACET_ID = "facetId" + const val KEY_IS_FIRST = "isFirst" + const val KEY_SUPPORTED_TRANSPORTS = "supportedTransports" + } +} + +fun Bundle?.withIsFirst(isFirst: Boolean) = Bundle(this ?: Bundle.EMPTY).apply { putBoolean(KEY_IS_FIRST, isFirst) } diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/Transport.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/Transport.kt new file mode 100644 index 000000000..0217cf89e --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/Transport.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +enum class Transport { + BLUETOOTH, + NFC, + USB, + SCREEN_LOCK +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/TransportSelectionFragment.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/TransportSelectionFragment.kt new file mode 100644 index 000000000..39063d55a --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/TransportSelectionFragment.kt @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import org.microg.gms.fido.core.databinding.FidoTransportSelectionFragmentBinding + +class TransportSelectionFragment : AuthenticatorActivityFragment() { + private lateinit var binding: FidoTransportSelectionFragmentBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FidoTransportSelectionFragmentBinding.inflate(inflater, container, false) + binding.data = data + binding.setOnScreenLockClick { + startScreenLockHandling() + } + return binding.root + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/WelcomeFragment.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/WelcomeFragment.kt new file mode 100644 index 000000000..60729a889 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/WelcomeFragment.kt @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import org.microg.gms.fido.core.R +import org.microg.gms.fido.core.databinding.FidoWelcomeFragmentBinding + +class WelcomeFragment : AuthenticatorActivityFragment() { + private lateinit var binding: FidoWelcomeFragmentBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FidoWelcomeFragmentBinding.inflate(inflater, container, false) + binding.data = data + binding.setOnGetStartedClick { + val next = data.supportedTransports.singleOrNull()?.let { + when (data.supportedTransports.first()) { + Transport.BLUETOOTH -> R.id.openBluetoothFragmentDirect + Transport.NFC -> R.id.openNfcFragmentDirect + Transport.USB -> R.id.openUsbFragmentDirect + Transport.SCREEN_LOCK -> R.id.openScreenLockFragmentDirect + } + } ?: R.id.openTransportSelectionFragment + findNavController().navigate(next, arguments.withIsFirst(false)) + } + return binding.root + } +} diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_bluetooth.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_bluetooth.xml new file mode 100644 index 000000000..d1b5b9246 --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_bluetooth.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_fingerprint.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_fingerprint.xml new file mode 100644 index 000000000..eba76e4e3 --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_fingerprint.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_key.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_key.xml new file mode 100644 index 000000000..67b8a97fc --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_key.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_nfc.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_nfc.xml new file mode 100644 index 000000000..8c9d47170 --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_nfc.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_usb.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_usb.xml new file mode 100644 index 000000000..e1772cd5f --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_usb.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/layout/fido_authenticator_activity.xml b/play-services-fido-core/src/main/res/layout/fido_authenticator_activity.xml new file mode 100644 index 000000000..cfbf3edfe --- /dev/null +++ b/play-services-fido-core/src/main/res/layout/fido_authenticator_activity.xml @@ -0,0 +1,11 @@ + + + + diff --git a/play-services-fido-core/src/main/res/layout/fido_transport_selection_fragment.xml b/play-services-fido-core/src/main/res/layout/fido_transport_selection_fragment.xml new file mode 100644 index 000000000..b5944020a --- /dev/null +++ b/play-services-fido-core/src/main/res/layout/fido_transport_selection_fragment.xml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-fido-core/src/main/res/layout/fido_welcome_fragment.xml b/play-services-fido-core/src/main/res/layout/fido_welcome_fragment.xml new file mode 100644 index 000000000..6661b9977 --- /dev/null +++ b/play-services-fido-core/src/main/res/layout/fido_welcome_fragment.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + +