From 8e98efdf0938fa14a56360cc3577d2f8182de011 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:41:31 +0900 Subject: [PATCH 01/63] Fido: Add support for fido authentication during Google Account sign-in https://github.com/microg/GmsCore/commit/ff5c5a7e1b24691d2c20de60119160493af50167 --- .../src/main/AndroidManifest.xml | 2 +- .../gms/auth/login/AssistantActivity.java | 4 +- .../microg/gms/auth/login/LoginActivity.java | 60 ++++++++++++++++++- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index da613f0d..385085d1 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -261,7 +261,7 @@ diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java index b4bea3f8..d53f38dd 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java @@ -17,7 +17,6 @@ package org.microg.gms.auth.login; import android.annotation.SuppressLint; -import android.app.Activity; import android.content.res.Configuration; import android.os.Bundle; import android.util.DisplayMetrics; @@ -26,10 +25,11 @@ import android.widget.Button; import android.widget.TextView; import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; import com.mgoogle.android.gms.R; -public abstract class AssistantActivity extends Activity { +public abstract class AssistantActivity extends AppCompatActivity { private static final int TITLE_MIN_HEIGHT = 64; private static final double TITLE_WIDTH_FACTOR = (8.0 / 18.0); 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 96d12f57..af9ce886 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 @@ -271,6 +271,10 @@ public class LoginActivity extends AssistantActivity { webView.loadUrl(buildUrl(tmpl, Utils.getLocale(this))); } + protected void runScript(String js) { + runOnUiThread(() -> webView.loadUrl("javascript:" + js)); + } + private void closeWeb(boolean programmaticAuth) { setMessage(R.string.auth_finalize); runOnUiThread(() -> webView.setVisibility(INVISIBLE)); @@ -406,12 +410,32 @@ public class LoginActivity extends AssistantActivity { Log.d(TAG, "JSBridge: addAccount"); } + @JavascriptInterface + public final void attemptLogin(String accountName, String password) { + Log.d(TAG, "JSBridge: attemptLogin"); + } + + @JavascriptInterface + public void backupSyncOptIn(String accountName) { + Log.d(TAG, "JSBridge: backupSyncOptIn"); + } + + @JavascriptInterface + public void clearOldLoginAttempts() { + Log.d(TAG, "JSBridge: clearOldLoginAttempts"); + } + @JavascriptInterface public final void closeView() { Log.d(TAG, "JSBridge: closeView"); closeWeb(false); } + @JavascriptInterface + public void fetchIIDToken(String entity) { + Log.d(TAG, "JSBridge: fetchIIDToken"); + } + @JavascriptInterface public final String fetchVerifiedPhoneNumber() { Log.d(TAG, "JSBridge: fetchVerifiedPhoneNumber"); @@ -446,12 +470,17 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final int getAuthModuleVersionCode() { - return 1; + return GMS_VERSION_CODE; } @JavascriptInterface public final int getBuildVersionSdk() { - return SDK_INT; + return Build.VERSION.SDK_INT; + } + + @JavascriptInterface + public int getDeviceContactsCount() { + return -1; } @JavascriptInterface @@ -525,15 +554,40 @@ public class LoginActivity extends AssistantActivity { } @JavascriptInterface - public final void setAccountIdentifier(String accountIdentifier) { + public final void setAccountIdentifier(String accountName) { Log.d(TAG, "JSBridge: setAccountIdentifier"); } + @JavascriptInterface + public void setAllActionsEnabled(boolean z) { + Log.d(TAG, "JSBridge: setAllActionsEnabled"); + } + @JavascriptInterface public final void setNewAccountCreated() { Log.d(TAG, "JSBridge: setNewAccountCreated"); } + @JavascriptInterface + public void setPrimaryActionEnabled(boolean z) { + Log.d(TAG, "JSBridge: setPrimaryActionEnabled"); + } + + @JavascriptInterface + public void setPrimaryActionLabel(String str, int i) { + Log.d(TAG, "JSBridge: setPrimaryActionLabel: " + str); + } + + @JavascriptInterface + public void setSecondaryActionEnabled(boolean z) { + Log.d(TAG, "JSBridge: setSecondaryActionEnabled"); + } + + @JavascriptInterface + public void setSecondaryActionLabel(String str, int i) { + Log.d(TAG, "JSBridge: setSecondaryActionLabel: " + str); + } + @JavascriptInterface public final void showKeyboard() { inputMethodManager.showSoftInput(webView, SHOW_IMPLICIT); From 5f278780e18babeda34ab08597facc8ac9602cb7 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:58:42 +0900 Subject: [PATCH 02/63] Allow synthetic field(s) in ObjectWrapper https://github.com/microg/GmsCore/commit/bc6bc583b9ebce160faab449cb62d947b5d791b9 --- .../android/gms/dynamic/ObjectWrapper.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java b/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java index 9158c941..ce938bbc 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java +++ b/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java @@ -18,6 +18,7 @@ package com.google.android.gms.dynamic; import android.os.IBinder; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.lang.reflect.Field; @@ -34,10 +35,13 @@ public class ObjectWrapper extends IObjectWrapper.Stub { if (obj == null) { return null; } + if (obj instanceof ObjectWrapper) { return ((ObjectWrapper) obj).t; } + IBinder binder = obj.asBinder(); +<<<<<<< HEAD Field[] fields = binder.getClass().getDeclaredFields(); if (fields.length < 1) { @@ -65,6 +69,9 @@ public class ObjectWrapper extends IObjectWrapper.Stub { if (field == null) { throw new IllegalArgumentException("No non-synthetic fields were found"); } +======= + Field field = getField(binder); +>>>>>>> 42253b04 (Allow synthetic field(s) in ObjectWrapper https://github.com/microg/GmsCore/commit/bc6bc583b9ebce160faab449cb62d947b5d791b9) if (!field.isAccessible()) { field.setAccessible(true); @@ -85,6 +92,35 @@ public class ObjectWrapper extends IObjectWrapper.Stub { } } + @NonNull + private static Field getField(IBinder binder) { + Field[] fields = binder.getClass().getDeclaredFields(); + + if (fields.length < 1) { + throw new IllegalArgumentException("No fields were found"); + } + + @Nullable + Field field = null; + + for (Field currentField : fields) { + if (currentField.isSynthetic()) { + continue; + } + + if (field == null) { + field = currentField; + } else { + throw new IllegalArgumentException("Too many non-synthetic fields were found"); + } + } + + if (field == null) { + throw new IllegalArgumentException("No non-synthetic fields were found"); + } + return field; + } + @Nullable public static T unwrapTyped(IObjectWrapper obj, Class clazz) { try { From b19fad128147ebbffaac26bc436ea8cd4025e2cf Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Fri, 22 Dec 2023 20:11:04 +0900 Subject: [PATCH 03/63] Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f --- build.gradle | 19 + .../gms/common/GoogleApiAvailability.java | 192 +++++++++ .../android/gms/common/api/AccountInfo.java | 2 +- .../google/android/gms/common/api/Api.java | 18 + .../android/gms/common/api/GoogleApi.java | 42 +- .../gms/common/api/GoogleApiActivity.java | 28 ++ .../gms/common/api/GoogleApiClient.java | 38 +- .../android/gms/common/api/PendingResult.java | 43 ++ .../ApiClient.java => DummyApiClient.java} | 28 +- .../java/org/microg/gms/common/GmsClient.java | 204 ++++++++++ .../org/microg/gms/common/GmsConnector.java | 92 +++++ .../gms/common/MultiConnectionKeeper.java | 229 +++++++++++ .../gms/common/api/AbstractPendingResult.java | 4 + .../gms/common/api/ApiClientBuilder.java | 2 +- .../gms/common/api/ApiClientSettings.java | 10 + .../gms/common/api/GoogleApiClientImpl.java | 102 ++++- .../gms/common/api/GoogleApiManager.java | 177 ++++++++ .../gms/common/api/PendingGoogleApiCall.java | 13 + .../gms/common/api/ResultCallbackHandler.java | 14 + .../common/api/ReturningGoogleApiCall.java | 22 + .../api/VoidReturningGoogleApiCall.java | 23 ++ play-services-basement/build.gradle | 2 +- .../gms/common/internal/ConnectionInfo.aidl | 7 + .../gms/common/internal/IGmsCallbacks.aidl | 8 + .../gms/common/internal/ConnectionInfo.java | 31 ++ .../internal/ValidateAccountRequest.java | 16 +- .../safeparcel/AbstractSafeParcelable.java | 24 ++ .../internal/safeparcel/SafeParcelReader.java | 328 +++++++++++++++ .../internal/safeparcel/SafeParcelWriter.java | 384 ++++++++++++++++++ .../internal/safeparcel/SafeParcelable.java | 55 +++ .../SafeParcelableCreatorAndWriter.java | 13 + .../safeparcel/SafeParcelableSerializer.java | 30 ++ .../main/java/org/microg/gms/common/Hide.java | 19 + .../java/org/microg/gms/common/PublicApi.java | 2 + .../gms/auth/login/AssistantActivity.java | 4 +- 35 files changed, 2204 insertions(+), 21 deletions(-) rename play-services-base/src/main/java/org/microg/gms/common/{api/ApiClient.java => DummyApiClient.java} (56%) create mode 100644 play-services-base/src/main/java/org/microg/gms/common/GmsClient.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/GmsConnector.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/api/ReturningGoogleApiCall.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/api/VoidReturningGoogleApiCall.java create mode 100644 play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ConnectionInfo.aidl create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/ConnectionInfo.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java create mode 100644 play-services-basement/src/main/java/org/microg/gms/common/Hide.java diff --git a/build.gradle b/build.gradle index 2c713c7c..3a471c38 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,13 @@ */ buildscript { +<<<<<<< HEAD ext.kotlinVersion = '1.8.20' +======= + ext.safeParcelVersion = '1.7.1' + + ext.kotlinVersion = '1.7.21' +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) ext.coroutineVersion = '1.6.4' ext.annotationVersion = '1.6.0' @@ -65,4 +71,17 @@ subprojects { mavenCentral() google() } +<<<<<<< HEAD } +======= + afterEvaluate { + // Temporary hack for Android Studio + if (project.plugins.hasPlugin('com.android.base')) { + dependencies { + compileOnly "org.microg:safe-parcel:$safeParcelVersion" + testCompileOnly "org.microg:safe-parcel:$safeParcelVersion" + } + } + } +} +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) diff --git a/play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java b/play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java index 61ee1ec0..50ff0e23 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java @@ -50,4 +50,196 @@ public class GoogleApiAvailability { return null; } +<<<<<<< HEAD +======= + /** + * Returns a PendingIntent to address the provided connection failure. + *

+ * If {@link ConnectionResult#hasResolution()} is true, then {@link ConnectionResult#getResolution()} + * will be returned. Otherwise, the returned PendingIntent will direct the user to either the + * Play Store if Google Play services is out of date or missing, or system settings if Google + * Play services is disabled on the device. + * + * @param context parent context for creating the PendingIntent. + * @param result the connection failure. If successful or the error is not resolvable by the user, null is returned. + */ + public PendingIntent getErrorResolutionPendingIntent(Context context, ConnectionResult result) { + if (result.hasResolution()) { + return result.getResolution(); + } + return getErrorResolutionPendingIntent(context, result.getErrorCode(), 0); + } + + /** + * Returns a PendingIntent to address the provided errorCode. It will direct the user to either + * the Play Store if Google Play services is out of date or missing, or system settings if + * Google Play services is disabled on the device. + * + * @param context parent context for creating the PendingIntent. + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param requestCode The requestCode given when calling startActivityForResult. + */ + public PendingIntent getErrorResolutionPendingIntent(Context context, int errorCode, int requestCode) { + // TODO + return null; + } + + /** + * Returns a human-readable string of the error code returned from {@link #isGooglePlayServicesAvailable(Context)}. + */ + public final String getErrorString(int errorCode) { + return ConnectionResult.getStatusString(errorCode); + } + + /** + * Verifies that Google Play services is installed and enabled on this device, and that the + * version installed on this device is no older than the one required by this client. + * + * @return status code indicating whether there was an error. Can be one of following in + * {@link ConnectionResult}: SUCCESS, SERVICE_MISSING, SERVICE_UPDATING, + * SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID + */ + public int isGooglePlayServicesAvailable(Context context) { + Log.d(TAG, "As we can't know right now if the later desired feature is available, " + + "we just pretend it to be."); + return SUCCESS; + } + + /** + * Verifies that Google Play services is installed and enabled on this device, and that the version installed on + * this device is no older than the one required by this client or the version is not older than the one specified + * in minApkVersion. + * + * @return status code indicating whether there was an error. Can be one of following in + * {@link ConnectionResult}: SUCCESS, SERVICE_MISSING, SERVICE_UPDATING, + * SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID + */ + public int isGooglePlayServicesAvailable(Context context, int minApkVersion) { + return isGooglePlayServicesAvailable(context); + } + + /** + * Determines whether an error can be resolved via user action. If true, proceed by calling + * {@link #getErrorDialog(Activity, int, int)} and showing the dialog. + * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)}, or + * returned to your application via {@link OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} + * @return true if the error is resolvable with {@link #getErrorDialog(Activity, int, int)} + */ + public final boolean isUserResolvableError(int errorCode) { + switch (errorCode) { + case SERVICE_MISSING: + case SERVICE_VERSION_UPDATE_REQUIRED: + case SERVICE_DISABLED: + case SERVICE_INVALID: + return true; + case SIGN_IN_REQUIRED: + case INVALID_ACCOUNT: + case RESOLUTION_REQUIRED: + case NETWORK_ERROR: + case INTERNAL_ERROR: + default: + return false; + } + } + + /** + * Attempts to make Google Play services available on this device. If Play Services is already + * available, the returned {@link Task} may complete immediately. + *

+ * If it is necessary to display UI in order to complete this request (e.g. sending the user + * to the Google Play store) the passed {@link Activity} will be used to display this UI. + *

+ * It is recommended to call this method from {@link Activity#onCreate(Bundle)}. + * If the passed {@link Activity} completes before the returned {@link Task} completes, the + * Task will fail with a {@link java.util.concurrent.CancellationException}. + *

+ * This method must be called from the main thread. + * + * @return A {@link Task}. If this Task completes without throwing an exception, Play Services + * is available on this device. + */ + public Task makeGooglePlayServicesAvailable(Activity activity) { + int status = isGooglePlayServicesAvailable(activity); + if (status == SUCCESS) { + return Tasks.forResult(null); + } + // TODO + return Tasks.forResult(null); + } + + /** + * Displays a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}. + * + * @param activity parent activity for creating the dialog, also used for identifying language to display dialog in. + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param requestCode The requestCode given when calling startActivityForResult. + * @return true if the dialog is shown, false otherwise + * @throws RuntimeException if API level is below 11 and activity is not a {@link FragmentActivity}. + * @see ErrorDialogFragment + * @see SupportErrorDialogFragmet + */ + public boolean showErrorDialogFragment(Activity activity, int errorCode, int requestCode) { + return showErrorDialogFragment(activity, errorCode, requestCode, null); + } + + /** + * Displays a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}. + * + * @param activity parent activity for creating the dialog, also used for identifying language to display dialog in. + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param requestCode The requestCode given when calling startActivityForResult. + * @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog is canceled. + * @return true if the dialog is shown, false otherwise + * @throws RuntimeException if API level is below 11 and activity is not a {@link FragmentActivity}. + */ + public boolean showErrorDialogFragment(Activity activity, int errorCode, int requestCode, DialogInterface.OnCancelListener cancelListener) { + Dialog dialog = getErrorDialog(activity, errorCode, requestCode, cancelListener); + if (dialog == null) { + return false; + } else { + // TODO + return false; + } + } + + /** + * Displays a notification for an error code returned from + * {@link #isGooglePlayServicesAvailable(Context)}, if it is resolvable by the user. + *

+ * This method is similar to {@link #getErrorDialog(int, android.app.Activity, int)}, but is + * provided for background tasks that cannot or should not display dialogs. + * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param context used for identifying language to display dialog in as well as accessing the + * {@link android.app.NotificationManager}. + */ + public void showErrorNotification(Context context, int errorCode) { + if (errorCode == RESOLUTION_REQUIRED) { + Log.e(TAG, "showErrorNotification(context, errorCode) is called for RESOLUTION_REQUIRED when showErrorNotification(context, result) should be called"); + } + + if (isUserResolvableError(errorCode)) { + GooglePlayServicesUtil.showErrorNotification(errorCode, context); + } + } + + /** + * Displays a notification for a connection failure, if it is resolvable by the user. + * + * @param context The calling context used to display the notification. + * @param result The connection failure. If successful or the error is not resolvable by the + * user, no notification is shown. + */ + public void showErrorNotification(Context context, ConnectionResult result) { + PendingIntent pendingIntent = getErrorResolutionPendingIntent(context, result); + if (pendingIntent != null) { + // TODO + } + } +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/AccountInfo.java b/play-services-base/src/main/java/com/google/android/gms/common/api/AccountInfo.java index fd400c45..30d6a169 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/AccountInfo.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/AccountInfo.java @@ -20,5 +20,5 @@ import org.microg.safeparcel.AutoSafeParcelable; @Deprecated public class AccountInfo extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(AccountInfo.class); + public static final Creator CREATOR = new AutoCreator(AccountInfo.class); } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java b/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java index 6b7f15e3..a6942637 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java @@ -16,6 +16,14 @@ package com.google.android.gms.common.api; +<<<<<<< HEAD +======= +import android.accounts.Account; + +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; + +import org.microg.gms.common.Hide; +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.ApiClientBuilder; @@ -67,4 +75,14 @@ public final class Api { } + @Hide + public interface Client { + void connect(); + + void disconnect(); + + boolean isConnected(); + + boolean isConnecting(); + } } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java index f0adb967..ff165974 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java @@ -7,22 +7,62 @@ package com.google.android.gms.common.api; import android.content.Context; +<<<<<<< HEAD +======= +import com.google.android.gms.common.api.internal.ApiKey; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; + +import org.microg.gms.common.Hide; +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.GoogleApiManager; @PublicApi +<<<<<<< HEAD public abstract class GoogleApi implements HasApiKey { @PublicApi(exclude = true) +======= +public abstract class GoogleApi implements HasApiKey { + private GoogleApiManager manager; + @Hide +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) public Api api; + @Hide + public O options; @PublicApi(exclude = true) protected GoogleApi(Context context, Api api) { this.api = api; +<<<<<<< HEAD GoogleApiManager.getInstance(); +======= + this.manager = GoogleApiManager.getInstance(context); + } + + @PublicApi(exclude = true) + protected GoogleApi(Context context, Api api, O options) { + this.api = api; + this.manager = GoogleApiManager.getInstance(context); + this.options = options; + } + + @PublicApi(exclude = true) + protected Task scheduleTask(PendingGoogleApiCall apiCall) { + TaskCompletionSource completionSource = new TaskCompletionSource<>(); + manager.scheduleTask(this, apiCall, completionSource); + return completionSource.getTask(); + } + + @Override + @PublicApi(exclude = true) + public ApiKey getApiKey() { + return null; +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } @PublicApi(exclude = true) public O getOptions() { - return null; + return options; } } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java index 8a6b0690..e5524cb9 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java @@ -1,6 +1,34 @@ +<<<<<<< HEAD package com.google.android.gms.common.api; import android.app.Activity; public class GoogleApiActivity extends Activity { +======= +/* + * SPDX-FileCopyrightText: 2020, 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.common.api; + +import android.app.Activity; +import android.content.DialogInterface; + +import org.microg.gms.common.PublicApi; + +/** + * Helper activity used by Google Play services APIs to display resolutions for connection errors. + */ +@PublicApi +public class GoogleApiActivity extends Activity implements DialogInterface.OnCancelListener { + @Override + @PublicApi(exclude = true) + public void onCancel(DialogInterface dialog) { + + } +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java index 0e20b6d5..a841fd98 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java @@ -111,7 +111,7 @@ public interface GoogleApiClient { * @see #registerConnectionFailedListener(OnConnectionFailedListener) * @see #unregisterConnectionFailedListener(OnConnectionFailedListener) */ - boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener); + public boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener); /** * Registers a listener to receive connection events from this {@link GoogleApiClient}. If the @@ -150,7 +150,7 @@ public interface GoogleApiClient { * @param listener the listener where the results of the asynchronous {@link #connect()} call * are delivered. */ - void registerConnectionFailedListener(OnConnectionFailedListener listener); + public void registerConnectionFailedListener(OnConnectionFailedListener listener); /** * Removes a connection listener from this {@link GoogleApiClient}. Note that removing a @@ -166,10 +166,24 @@ public interface GoogleApiClient { @PublicApi class Builder { private final Context context; +<<<<<<< HEAD private final Map apis = new HashMap<>(); private final Set connectionCallbacks = new HashSet<>(); private final Set connectionFailedListeners = new HashSet<>(); private final Looper looper; +======= + private final Map apis = new HashMap(); + private final Set connectionCallbacks = new HashSet(); + private final Set connectionFailedListeners = new HashSet(); + private final Set scopes = new HashSet(); + private String accountName; + private int clientId = -1; + private FragmentActivity fragmentActivity; + private Looper looper; + private int gravityForPopups; + private OnConnectionFailedListener unresolvedConnectionFailedListener; + private View viewForPopups; +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) public Builder(Context context) { this.context = context; @@ -198,7 +212,12 @@ public interface GoogleApiClient { } private ApiClientSettings getClientSettings() { - return null; + ApiClientSettings clientSettings = new ApiClientSettings(); + clientSettings.accountName = accountName; + clientSettings.scopes = new HashSet<>(scopes); + clientSettings.gravityForPopups = gravityForPopups; + clientSettings.viewForPopups = viewForPopups; + return clientSettings; } } @@ -210,6 +229,19 @@ public interface GoogleApiClient { @PublicApi @Deprecated interface ConnectionCallbacks extends org.microg.gms.common.api.ConnectionCallbacks { +<<<<<<< HEAD +======= + /** + * A suspension cause informing that the service has been killed. + */ + int CAUSE_SERVICE_DISCONNECTED = 1; + /** + * A suspension cause informing you that a peer device connection was lost. + */ + int CAUSE_NETWORK_LOST = 2; + + void onConnected(Bundle connectionHint); +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } @PublicApi diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java b/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java index e5f92160..1e5c462f 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java @@ -16,5 +16,48 @@ package com.google.android.gms.common.api; +<<<<<<< HEAD public interface PendingResult { +======= +import java.util.concurrent.TimeUnit; + +/** + * Represents a pending result from calling an API method in Google Play services. The final result + * object from a PendingResult is of type R, which can be retrieved in one of two ways. + *

+ *

    + *
  • via blocking calls to {@link #await()}, or {@link #await(long, TimeUnit)}, or
  • + *
  • via a callback by passing in an object implementing interface {@link ResultCallback} to + * {@link #setResultCallback(ResultCallback)}.
  • + *
+ * After the result has been retrieved using {@link #await()} or delivered to the result callback, + * it is an error to attempt to retrieve the result again. It is the responsibility of the caller + * or callback receiver to release any resources associated with the returned result. Some result + * types may implement {@link Releasable}, in which case {@link Releasable#release()} should be + * used to free the associated resources. + *

+ * TODO: Docs + */ +public interface PendingResult { + /** + * Blocks until the task is completed. This is not allowed on the UI thread. The returned + * result object can have an additional failure mode of INTERRUPTED. + */ + public R await(); + + /** + * Blocks until the task is completed or has timed out waiting for the result. This is not + * allowed on the UI thread. The returned result object can have an additional failure mode + * of either INTERRUPTED or TIMEOUT. + */ + public R await(long time, TimeUnit unit); + + public void cancel(); + + public boolean isCanceled(); + + public void setResultCallback(ResultCallback callback, long time, TimeUnit unit); + + public void setResultCallback(ResultCallback callback); +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java b/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java similarity index 56% rename from play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java rename to play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java index 602e150e..c527866f 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java +++ b/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java @@ -14,14 +14,30 @@ * limitations under the License. */ -package org.microg.gms.common.api; +package org.microg.gms.common; -public interface ApiClient { - void connect(); +import com.google.android.gms.common.api.Api; - void disconnect(); +public class DummyApiClient implements Api.Client { + private boolean connected = false; - boolean isConnected(); + @Override + public void connect() { + connected = true; + } - boolean isConnecting(); + @Override + public void disconnect() { + connected = false; + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public boolean isConnecting() { + return false; + } } 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 new file mode 100644 index 00000000..e5b71717 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package org.microg.gms.common; + +import android.accounts.Account; +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; +import android.util.Log; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.Api; +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; +import com.google.android.gms.common.internal.IGmsServiceBroker; + +import org.microg.gms.common.api.ConnectionCallbacks; +import org.microg.gms.common.api.OnConnectionFailedListener; + +public abstract class GmsClient implements Api.Client { + private static final String TAG = "GmsClient"; + + private final Context context; + protected final ConnectionCallbacks callbacks; + protected final OnConnectionFailedListener connectionFailedListener; + protected ConnectionState state = ConnectionState.NOT_CONNECTED; + private ServiceConnection serviceConnection; + private I serviceInterface; + private final String actionString; + + protected boolean requireMicrog; + protected String packageName; + + protected int serviceId = -1; + protected Account account = null; + protected Bundle extras = new Bundle(); + + public GmsClient(Context context, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener, String actionString) { + this.context = context; + this.callbacks = callbacks; + this.connectionFailedListener = connectionFailedListener; + this.actionString = actionString; + this.requireMicrog = false; + this.packageName = context.getPackageName(); + } + + protected void onConnectedToBroker(IGmsServiceBroker broker, GmsCallbacks callbacks) throws RemoteException { + if (serviceId == -1) { + throw new IllegalStateException("Service ID not set in constructor and onConnectedToBroker not implemented"); + } + GetServiceRequest request = new GetServiceRequest(serviceId); + request.packageName = packageName; + request.account = account; + request.extras = extras; + broker.getService(callbacks, request); + } + + protected abstract I interfaceFromBinder(IBinder binder); + + @Override + public synchronized void connect() { + Log.d(TAG, "connect()"); + if (state == ConnectionState.CONNECTED || state == ConnectionState.CONNECTING) { + Log.d(TAG, "Already connected/connecting - nothing to do"); + } + state = ConnectionState.CONNECTING; + if (serviceConnection != null) { + MultiConnectionKeeper.getInstance(context).unbind(actionString, serviceConnection); + } + serviceConnection = new GmsServiceConnection(); + if (!MultiConnectionKeeper.getInstance(context).bind(actionString, serviceConnection, requireMicrog)) { + state = ConnectionState.ERROR; + handleConnectionFailed(); + } + } + + public void handleConnectionFailed() { + connectionFailedListener.onConnectionFailed(new ConnectionResult(ConnectionResult.API_UNAVAILABLE, null)); + } + + @Override + public synchronized void disconnect() { + Log.d(TAG, "disconnect()"); + if (state == ConnectionState.DISCONNECTING) return; + if (state == ConnectionState.CONNECTING) { + state = ConnectionState.DISCONNECTING; + return; + } + serviceInterface = null; + if (serviceConnection != null) { + MultiConnectionKeeper.getInstance(context).unbind(actionString, serviceConnection); + serviceConnection = null; + } + state = ConnectionState.NOT_CONNECTED; + } + + @Override + public synchronized boolean isConnected() { + return (state == ConnectionState.CONNECTED && serviceInterface != null && serviceInterface.asBinder().isBinderAlive() ) || state == ConnectionState.PSEUDO_CONNECTED; + } + + @Override + public synchronized boolean isConnecting() { + return state == ConnectionState.CONNECTING; + } + + public synchronized boolean hasError() { + return state == ConnectionState.ERROR; + } + + public Context getContext() { + return context; + } + + public synchronized I getServiceInterface() { + if (isConnecting()) { + // TODO: wait for connection to be established and return afterwards. + throw new IllegalStateException("Waiting for connection"); + } else if (!isConnected()) { + throw new IllegalStateException("interface only available once connected!"); + } + return serviceInterface; + } + + protected enum ConnectionState { + NOT_CONNECTED, CONNECTING, CONNECTED, DISCONNECTING, ERROR, PSEUDO_CONNECTED + } + + private class GmsServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + try { + Log.d(TAG, "ServiceConnection : onServiceConnected(" + componentName + ")"); + onConnectedToBroker(IGmsServiceBroker.Stub.asInterface(iBinder), new GmsCallbacks()); + } catch (RemoteException e) { + disconnect(); + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + synchronized (GmsClient.this) { + state = ConnectionState.NOT_CONNECTED; + } + } + } + + public class GmsCallbacks extends IGmsCallbacks.Stub { + + @Override + public void onPostInitComplete(int statusCode, IBinder binder, Bundle params) + throws RemoteException { + 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; + serviceInterface = interfaceFromBinder(binder); + } + Log.d(TAG, "GmsCallbacks : onPostInitComplete(" + serviceInterface + ")"); + callbacks.onConnected(params); + } + } + + @Override + public void onAccountValidationComplete(int statusCode, Bundle params) throws RemoteException { + Log.d(TAG, "GmsCallbacks : onAccountValidationComplete"); + } + + @Override + public void onPostInitCompleteWithConnectionInfo(int statusCode, IBinder binder, ConnectionInfo info) throws RemoteException { + onPostInitComplete(statusCode, binder, info == null ? null : info.params); + } + } + +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/GmsConnector.java b/play-services-base/src/main/java/org/microg/gms/common/GmsConnector.java new file mode 100644 index 00000000..b77c6d2c --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/GmsConnector.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package org.microg.gms.common; + +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Result; + +import org.microg.gms.common.api.AbstractPendingResult; +import org.microg.gms.common.api.GoogleApiClientImpl; + +public class GmsConnector { + private static final String TAG = "GmsConnector"; + + private final GoogleApiClientImpl apiClient; + private final Api api; + private final Callback callback; + + public GmsConnector(GoogleApiClient apiClient, Api api, Callback callback) { + this.apiClient = (GoogleApiClientImpl) apiClient; + this.api = api; + this.callback = callback; + } + + public static PendingResult call(GoogleApiClient client, Api api, GmsConnector.Callback callback) { + return new GmsConnector(client, api, callback).connect(); + } + + public AbstractPendingResult connect() { + Log.d(TAG, "connect()"); + apiClient.incrementUsageCounter(); + apiClient.getApiConnection(api); + Looper looper = apiClient.getLooper(); + final AbstractPendingResult result = new AbstractPendingResult(looper); + Message msg = new Message(); + msg.obj = result; + new Handler(looper).sendMessage(msg); + return result; + } + + public interface Callback { + void onClientAvailable(C client, ResultProvider resultProvider) throws RemoteException; + + interface ResultProvider { + void onResultAvailable(R result); + } + } + + private class Handler extends android.os.Handler { + private Handler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "Handler : handleMessage"); + final AbstractPendingResult result = (AbstractPendingResult) msg.obj; + try { + C connection = (C) apiClient.getApiConnection(api); + callback.onClientAvailable(connection, new GmsConnector.Callback.ResultProvider() { + @Override + public void onResultAvailable(R realResult) { + result.deliverResult(realResult); + apiClient.decrementUsageCounter(); + } + }); + } catch (RemoteException ignored) { + + } + } + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java b/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java new file mode 100644 index 00000000..30c1d61c --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package org.microg.gms.common; + +import android.annotation.SuppressLint; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.content.pm.ResolveInfo; +import android.os.IBinder; +import android.util.Log; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; +import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; + +public class MultiConnectionKeeper { + private static final String TAG = "GmsMultiConKeeper"; + + private static MultiConnectionKeeper INSTANCE; + + private final Context context; + private final Map connections = new HashMap(); + + public MultiConnectionKeeper(Context context) { + this.context = context; + } + + public synchronized static MultiConnectionKeeper getInstance(Context context) { + if (INSTANCE == null) + INSTANCE = new MultiConnectionKeeper(context.getApplicationContext()); + return INSTANCE; + } + + public synchronized boolean bind(String action, ServiceConnection connection) { + return bind(action, connection, false); + } + + public synchronized boolean bind(String action, ServiceConnection connection, boolean requireMicrog) { + Connection con = connections.get(action); + Log.d(TAG, "bind(" + action + ", " + connection + ", " + requireMicrog + ") has=" + (con != null)); + if (con != null) { + if (!con.forwardsConnection(connection)) { + con.addConnectionForward(connection); + if (!con.isBound()) + con.bind(); + } + } else { + con = new Connection(action, requireMicrog); + con.addConnectionForward(connection); + con.bind(); + connections.put(action, con); + } + Log.d(TAG, "bind() : bound=" + con.isBound()); + return con.isBound(); + } + + public synchronized void unbind(String action, ServiceConnection connection) { + Log.d(TAG, "unbind(" + action + ", " + connection + ")"); + Connection con = connections.get(action); + if (con != null) { + con.removeConnectionForward(connection); + if (con.isBound()) { + if (!con.hasForwards()) { + con.unbind(); + connections.remove(action); + } else { + Log.d(TAG, "Not unbinding for " + connection + ": has pending other bindings on action " + action); + } + } + } + } + + public class Connection { + private final String actionString; + private final boolean requireMicrog; + private final Set connectionForwards = new HashSet(); + private boolean bound = false; + private boolean connected = false; + private IBinder binder; + private ComponentName component; + private ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + Log.d(TAG, "Connection(" + actionString + ") : ServiceConnection : " + + "onServiceConnected(" + componentName + ")"); + binder = iBinder; + component = componentName; + for (ServiceConnection connection : connectionForwards) { + connection.onServiceConnected(componentName, iBinder); + } + connected = true; + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + Log.d(TAG, "Connection(" + actionString + ") : ServiceConnection : " + + "onServiceDisconnected(" + componentName + ")"); + binder = null; + component = componentName; + for (ServiceConnection connection : connectionForwards) { + connection.onServiceDisconnected(componentName); + } + connected = false; + bound = false; + } + }; + + public Connection(String actionString) { + this(actionString, false); + } + + public Connection(String actionString, boolean requireMicrog) { + this.actionString = actionString; + this.requireMicrog = requireMicrog; + } + + @SuppressLint("InlinedApi") + public void bind() { + Log.d(TAG, "Connection(" + actionString + ") : bind()"); + Intent gmsIntent = new Intent(actionString).setPackage(GMS_PACKAGE_NAME); + Intent selfIntent = new Intent(actionString).setPackage(context.getPackageName()); + Intent intent; + ResolveInfo resolveInfo; + if ((resolveInfo = context.getPackageManager().resolveService(gmsIntent, 0)) == null) { + Log.w(TAG, "No GMS service found for " + actionString); + if (context.getPackageManager().resolveService(selfIntent, 0) != null) { + Log.d(TAG, "Found service for " + actionString + " in self package, using it instead"); + intent = selfIntent; + } else { + return; + } + } else if (requireMicrog && !isMicrog(resolveInfo)) { + Log.w(TAG, "GMS service found for " + actionString + " but looks not like microG"); + if (context.getPackageManager().resolveService(selfIntent, 0) != null) { + Log.d(TAG, "Found service for " + actionString + " in self package, using it instead"); + intent = selfIntent; + } else { + intent = gmsIntent; + } + } else { + intent = gmsIntent; + } + int flags = Context.BIND_AUTO_CREATE | Context.BIND_DEBUG_UNBIND; + if (SDK_INT >= ICE_CREAM_SANDWICH) { + flags |= Context.BIND_ADJUST_WITH_ACTIVITY; + } + bound = context.bindService(intent, serviceConnection, flags); + Log.d(TAG, "Connection(" + actionString + ") : bind() : bindService=" + bound); + if (!bound) { + context.unbindService(serviceConnection); + } + } + + public boolean isMicrog(ResolveInfo resolveInfo) { + if (resolveInfo == null || resolveInfo.serviceInfo == null) return false; + if (resolveInfo.serviceInfo.name.startsWith("org.microg.")) return true; + try { + PermissionInfo info = context.getPackageManager().getPermissionInfo("org.microg.gms.EXTENDED_ACCESS", 0); + return info.packageName.equals(resolveInfo.serviceInfo.packageName); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + public boolean isBound() { + return bound; + } + + public IBinder getBinder() { + return binder; + } + + public void unbind() { + Log.d(TAG, "Connection(" + actionString + ") : unbind()"); + try { + context.unbindService(serviceConnection); + } catch (IllegalArgumentException e) { // not bound (whatever reason) + Log.w(TAG, e); + } + bound = false; + } + + public void addConnectionForward(ServiceConnection connection) { + connectionForwards.add(connection); + if (connected) { + connection.onServiceConnected(component, binder); + } + } + + public void removeConnectionForward(ServiceConnection connection) { + connectionForwards.remove(connection); + if (connected) { + connection.onServiceDisconnected(component); + } + } + + public boolean forwardsConnection(ServiceConnection connection) { + return connectionForwards.contains(connection); + } + + public boolean hasForwards() { + return !connectionForwards.isEmpty(); + } + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java index cea4229b..df6b96e3 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java @@ -23,7 +23,11 @@ import com.google.android.gms.common.api.PendingResult; public class AbstractPendingResult implements PendingResult { public AbstractPendingResult(Looper looper) { +<<<<<<< HEAD new ResultCallbackHandler<>(looper); +======= + handler = new ResultCallbackHandler(looper); +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientBuilder.java b/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientBuilder.java index b5aa386f..18336fa5 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientBuilder.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientBuilder.java @@ -22,5 +22,5 @@ import android.os.Looper; import com.google.android.gms.common.api.Api; public interface ApiClientBuilder { - ApiClient build(O options, Context context, Looper looper, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener); + Api.Client build(O options, Context context, Looper looper, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener); } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientSettings.java b/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientSettings.java index d1e314a7..af67e799 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientSettings.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ApiClientSettings.java @@ -5,5 +5,15 @@ package org.microg.gms.common.api; +import android.view.View; + +import java.util.Set; + public class ApiClientSettings { + public String accountName; + public String packageName; + public Integer sessionId; + public Set scopes; + public int gravityForPopups; + public View viewForPopups; } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java index 371a6555..2e023fde 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java @@ -19,8 +19,15 @@ package org.microg.gms.common.api; import android.content.Context; import android.os.Bundle; import android.os.Looper; + +<<<<<<< HEAD +======= +import androidx.fragment.app.FragmentActivity; + import android.util.Log; +import com.google.android.gms.common.ConnectionResult; +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; @@ -32,9 +39,48 @@ import java.util.Set; public class GoogleApiClientImpl implements GoogleApiClient { private static final String TAG = "GmsApiClientImpl"; +<<<<<<< HEAD private final Map apiConnections = new HashMap<>(); private final Set connectionCallbacks = new HashSet<>(); private final Set connectionFailedListeners = new HashSet<>(); +======= + private final Context context; + private final Looper looper; + private final ApiClientSettings clientSettings; + private final Map apis = new HashMap(); + private final Map apiConnections = new HashMap(); + private final Set connectionCallbacks = new HashSet(); + private final Set connectionFailedListeners = new HashSet(); + private final int clientId; + private final ConnectionCallbacks baseConnectionCallbacks = new ConnectionCallbacks() { + @Override + public void onConnected(Bundle connectionHint) { + Log.d(TAG, "ConnectionCallbacks : onConnected()"); + for (ConnectionCallbacks callback : connectionCallbacks) { + callback.onConnected(connectionHint); + } + } + + @Override + public void onConnectionSuspended(int cause) { + Log.d(TAG, "ConnectionCallbacks : onConnectionSuspended()"); + for (ConnectionCallbacks callback : connectionCallbacks) { + callback.onConnectionSuspended(cause); + } + } + }; + private final OnConnectionFailedListener baseConnectionFailedListener = new + OnConnectionFailedListener() { + @Override + public void onConnectionFailed(ConnectionResult result) { + Log.d(TAG, "OnConnectionFailedListener : onConnectionFailed()"); + for (OnConnectionFailedListener listener : connectionFailedListeners) { + listener.onConnectionFailed(result); + } + } + }; + private int usageCounter = 0; +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) private boolean shouldDisconnect = false; public GoogleApiClientImpl(Context context, Looper looper, ApiClientSettings clientSettings, @@ -44,6 +90,10 @@ public class GoogleApiClientImpl implements GoogleApiClient { this.connectionCallbacks.addAll(connectionCallbacks); this.connectionFailedListeners.addAll(connectionFailedListeners); + if (this.clientSettings.sessionId == null) { + this.clientSettings.sessionId = hashCode(); + } + for (Api api : apis.keySet()) { ConnectionCallbacks baseConnectionCallbacks = new ConnectionCallbacks() { @Override @@ -72,6 +122,41 @@ public class GoogleApiClientImpl implements GoogleApiClient { } } +<<<<<<< HEAD +======= + public synchronized void incrementUsageCounter() { + usageCounter++; + } + + public synchronized void decrementUsageCounter() { + usageCounter--; + if (shouldDisconnect) disconnect(); + } + + public Looper getLooper() { + return looper; + } + + public Api.Client getApiConnection(Api api) { + return apiConnections.get(api); + } + + @Override + public ConnectionResult blockingConnect() { + return null; + } + + @Override + public ConnectionResult blockingConnect(long timeout, TimeUnit unit) { + return null; + } + + @Override + public PendingResult clearDefaultAccountAndReconnect() { + return null; + } + +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) @Override public synchronized void connect() { Log.d(TAG, "connect()"); @@ -83,7 +168,7 @@ public class GoogleApiClientImpl implements GoogleApiClient { Log.d(TAG, "Already connected/connecting, nothing to do"); return; } - for (ApiClient connection : apiConnections.values()) { + for (Api.Client connection : apiConnections.values()) { if (!connection.isConnected()) { connection.connect(); } @@ -92,17 +177,28 @@ public class GoogleApiClientImpl implements GoogleApiClient { @Override public synchronized void disconnect() { +<<<<<<< HEAD Log.d(TAG, "disconnect()"); for (ApiClient connection : apiConnections.values()) { if (connection.isConnected()) { connection.disconnect(); +======= + if (usageCounter > 0) { + shouldDisconnect = true; + } else { + Log.d(TAG, "disconnect()"); + for (Api.Client connection : apiConnections.values()) { + if (connection.isConnected()) { + connection.disconnect(); + } +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } } } @Override public synchronized boolean isConnected() { - for (ApiClient connection : apiConnections.values()) { + for (Api.Client connection : apiConnections.values()) { if (!connection.isConnected()) return false; } return true; @@ -110,7 +206,7 @@ public class GoogleApiClientImpl implements GoogleApiClient { @Override public synchronized boolean isConnecting() { - for (ApiClient connection : apiConnections.values()) { + for (Api.Client connection : apiConnections.values()) { if (connection.isConnecting()) return true; } return false; diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java index a5fa2545..d30293ed 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java @@ -5,11 +5,33 @@ package org.microg.gms.common.api; +<<<<<<< HEAD import android.annotation.SuppressLint; +======= +import android.content.Context; +import android.os.Bundle; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApi; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) public class GoogleApiManager { @SuppressLint("StaticFieldLeak") private static GoogleApiManager instance; +<<<<<<< HEAD +======= + private Context context; + private Map clientMap = new HashMap<>(); + private Map>> waitingApiCallMap = new HashMap<>(); +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) private GoogleApiManager() { } @@ -18,4 +40,159 @@ public class GoogleApiManager { if (instance == null) instance = new GoogleApiManager(); } +<<<<<<< HEAD +======= + private synchronized A clientForApi(GoogleApi api) { + ApiInstance apiInstance = new ApiInstance(api); + if (clientMap.containsKey(apiInstance)) { + return (A) clientMap.get(apiInstance); + } else { + Api.Client client = api.api.getBuilder().build(api.getOptions(), context, context.getMainLooper(), null, new ConnectionCallback(apiInstance), new ConnectionFailedListener(apiInstance)); + clientMap.put(apiInstance, client); + waitingApiCallMap.put(apiInstance, new ArrayList<>()); + return (A) client; + } + } + + public synchronized void scheduleTask(GoogleApi api, PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { + A client = clientForApi(api); + boolean connecting = client.isConnecting(); + boolean connected = client.isConnected(); + if (connected) { + try { + apiCall.execute(client, completionSource); + } catch (Exception e) { + completionSource.setException(e); + } + } else { + waitingApiCallMap.get(new ApiInstance(api)).add(new WaitingApiCall((PendingGoogleApiCall) apiCall, completionSource)); + if (!connecting) { + client.connect(); + } + } + } + + private synchronized void onInstanceConnected(ApiInstance apiInstance, Bundle connectionHint) { + List> waitingApiCalls = waitingApiCallMap.get(apiInstance); + for (WaitingApiCall waitingApiCall : waitingApiCalls) { + try { + waitingApiCall.execute(clientMap.get(apiInstance)); + } catch (Exception e) { + waitingApiCall.failed(e); + } + } + waitingApiCalls.clear(); + } + + private synchronized void onInstanceSuspended(ApiInstance apiInstance, int cause) { + + } + + private synchronized void onInstanceFailed(ApiInstance apiInstance, ConnectionResult result) { + List> waitingApiCalls = waitingApiCallMap.get(apiInstance); + for (WaitingApiCall waitingApiCall : waitingApiCalls) { + waitingApiCall.failed(new RuntimeException(result.getErrorMessage())); + } + waitingApiCalls.clear(); + } + + private class ConnectionCallback implements ConnectionCallbacks { + private ApiInstance apiInstance; + + public ConnectionCallback(ApiInstance apiInstance) { + this.apiInstance = apiInstance; + } + + @Override + public void onConnected(Bundle connectionHint) { + onInstanceConnected(apiInstance, connectionHint); + } + + @Override + public void onConnectionSuspended(int cause) { + onInstanceSuspended(apiInstance, cause); + } + } + + private class ConnectionFailedListener implements OnConnectionFailedListener { + private ApiInstance apiInstance; + + public ConnectionFailedListener(ApiInstance apiInstance) { + this.apiInstance = apiInstance; + } + + @Override + public void onConnectionFailed(ConnectionResult result) { + onInstanceFailed(apiInstance, result); + } + } + + private static class WaitingApiCall { + private PendingGoogleApiCall apiCall; + private TaskCompletionSource completionSource; + + public WaitingApiCall(PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { + this.apiCall = apiCall; + this.completionSource = completionSource; + } + + public void execute(Api.Client client) throws Exception { + apiCall.execute(client, completionSource); + } + + public void failed(Exception e) { + completionSource.setException(e); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WaitingApiCall that = (WaitingApiCall) o; + + if (apiCall != null ? !apiCall.equals(that.apiCall) : that.apiCall != null) return false; + return completionSource != null ? completionSource.equals(that.completionSource) : that.completionSource == null; + } + + @Override + public int hashCode() { + int result = apiCall != null ? apiCall.hashCode() : 0; + result = 31 * result + (completionSource != null ? completionSource.hashCode() : 0); + return result; + } + } + + private static class ApiInstance { + private Class apiClass; + private Api.ApiOptions apiOptions; + + public ApiInstance(Class apiClass, Api.ApiOptions apiOptions) { + this.apiClass = apiClass; + this.apiOptions = apiOptions; + } + + public ApiInstance(GoogleApi api) { + this(api.getClass(), api.getOptions()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ApiInstance that = (ApiInstance) o; + + if (apiClass != null ? !apiClass.equals(that.apiClass) : that.apiClass != null) return false; + return apiOptions != null ? apiOptions.equals(that.apiOptions) : that.apiOptions == null; + } + + @Override + public int hashCode() { + int result = apiClass != null ? apiClass.hashCode() : 0; + result = 31 * result + (apiOptions != null ? apiOptions.hashCode() : 0); + return result; + } + } +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java b/play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java new file mode 100644 index 00000000..578d02b9 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/PendingGoogleApiCall.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.common.api; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.tasks.TaskCompletionSource; + +public interface PendingGoogleApiCall { + void execute(A client, TaskCompletionSource completionSource) throws Exception; +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java b/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java index a737a076..3401f596 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java @@ -47,6 +47,20 @@ class ResultCallbackHandler extends Handler { } } +<<<<<<< HEAD +======= + public void sendResultCallback(ResultCallback callback, R result) { + Message message = new Message(); + message.what = CALLBACK_ON_COMPLETE; + message.obj = new OnCompleteObject(callback, result); + sendMessage(message); + } + + public void sendTimeoutResultCallback(AbstractPendingResult pendingResult, long millis) { + + } + +>>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) public static class OnCompleteObject { public ResultCallback callback; public R result; diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ReturningGoogleApiCall.java b/play-services-base/src/main/java/org/microg/gms/common/api/ReturningGoogleApiCall.java new file mode 100644 index 00000000..f836b9d4 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ReturningGoogleApiCall.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.common.api; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.tasks.TaskCompletionSource; + +public interface ReturningGoogleApiCall extends PendingGoogleApiCall { + R execute(A client) throws Exception; + + @Override + default void execute(A client, TaskCompletionSource completionSource) { + try { + completionSource.setResult(execute(client)); + } catch (Exception e) { + completionSource.setException(e); + } + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/VoidReturningGoogleApiCall.java b/play-services-base/src/main/java/org/microg/gms/common/api/VoidReturningGoogleApiCall.java new file mode 100644 index 00000000..ee8cbf66 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/VoidReturningGoogleApiCall.java @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.common.api; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.tasks.TaskCompletionSource; + +public interface VoidReturningGoogleApiCall extends PendingGoogleApiCall{ + void execute(A client) throws Exception; + + @Override + default void execute(A client, TaskCompletionSource completionSource) { + try { + execute(client); + completionSource.setResult(null); + } catch (Exception e) { + completionSource.setException(e); + } + } +} diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index 8971fb98..bbc86b99 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -18,7 +18,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' dependencies { - api "org.microg:safe-parcel:1.7.0" + api "org.microg:safe-parcel:$safeParcelVersion" // Dependencies from play-services-basement:18.0.0 api "androidx.collection:collection:$collectionVersion" diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ConnectionInfo.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ConnectionInfo.aidl new file mode 100644 index 00000000..b393f11d --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ConnectionInfo.aidl @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal; +parcelable ConnectionInfo; diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsCallbacks.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsCallbacks.aidl index b0210281..8fe63347 100644 --- a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsCallbacks.aidl +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsCallbacks.aidl @@ -1,7 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + package com.google.android.gms.common.internal; import android.os.Bundle; +import com.google.android.gms.common.internal.ConnectionInfo; interface IGmsCallbacks { void onPostInitComplete(int statusCode, IBinder binder, in Bundle params); + void onAccountValidationComplete(int statusCode, in Bundle params); + void onPostInitCompleteWithConnectionInfo(int statusCode, IBinder binder, in ConnectionInfo info); } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/ConnectionInfo.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/ConnectionInfo.java new file mode 100644 index 00000000..19326d08 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/ConnectionInfo.java @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal; + +import android.os.Bundle; +import android.os.Parcel; +import androidx.annotation.NonNull; +import com.google.android.gms.common.Feature; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class ConnectionInfo extends AbstractSafeParcelable { + @Field(1) + public Bundle params; + @Field(2) + public Feature[] features; + @Field(3) + public int unknown3; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ConnectionInfo.class); +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/ValidateAccountRequest.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/ValidateAccountRequest.java index 75e59d95..1cf0ecc1 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/internal/ValidateAccountRequest.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/ValidateAccountRequest.java @@ -16,8 +16,18 @@ package com.google.android.gms.common.internal; -import org.microg.safeparcel.AutoSafeParcelable; +import android.os.Parcel; +import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; -public class ValidateAccountRequest extends AutoSafeParcelable { - public static Creator CREATOR = new AutoCreator<>(ValidateAccountRequest.class); +@SafeParcelable.Class +public class ValidateAccountRequest extends AbstractSafeParcelable { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = findCreator(ValidateAccountRequest.class); } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java new file mode 100644 index 00000000..ec7d84d5 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +public abstract class AbstractSafeParcelable implements SafeParcelable { + + @SuppressWarnings("unchecked") + public static SafeParcelableCreatorAndWriter findCreator(java.lang.Class tClass) { + String creatorClassName = tClass.getName() + "$000Creator"; + try { + return (SafeParcelableCreatorAndWriter) java.lang.Class.forName(creatorClassName).newInstance(); + } catch (Exception e) { + throw new RuntimeException("No Creator found for " + tClass.getName(), e); + } + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java new file mode 100644 index 00000000..226f50e8 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java @@ -0,0 +1,328 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.HashMap; + +@SuppressWarnings("MagicNumber") +public final class SafeParcelReader { + + private SafeParcelReader() { + } + + @Deprecated + public static int halfOf(int i) { + return i & 0xFFFF; + } + + public static int getFieldId(int header) { + return header & 0xFFFF; + } + + @Deprecated + public static int readSingleInt(Parcel parcel) { + return parcel.readInt(); + } + + public static int readHeader(Parcel parcel) { + return parcel.readInt(); + } + + private static int readSize(Parcel parcel, int header) { + if ((header & 0xFFFF0000) != 0xFFFF0000) + return header >> 16 & 0xFFFF; + return parcel.readInt(); + } + + private static void readExpectedSize(Parcel parcel, int header, int expectedSize) { + int i = readSize(parcel, header); + if (i != expectedSize) + throw new ReadException("Expected size " + expectedSize + " got " + i + " (0x" + Integer.toHexString(i) + ")", parcel); + } + + @Deprecated + public static int readStart(Parcel parcel) { + return readObjectHeader(parcel); + } + + public static int readObjectHeader(Parcel parcel) { + int header = readHeader(parcel); + int size = readSize(parcel, header); + int start = parcel.dataPosition(); + if (getFieldId(header) != SafeParcelable.SAFE_PARCEL_OBJECT_MAGIC) + throw new ReadException("Expected object header. Got 0x" + Integer.toHexString(header), parcel); + int end = start + size; + if ((end < start) || (end > parcel.dataSize())) + throw new ReadException("Size read is invalid start=" + start + " end=" + end, parcel); + return end; + } + + public static int readInt(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readInt(); + } + + public static byte readByte(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return (byte) parcel.readInt(); + } + + public static short readShort(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return (short) parcel.readInt(); + } + + public static boolean readBool(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readInt() != 0; + } + + public static long readLong(Parcel parcel, int header) { + readExpectedSize(parcel, header, 8); + return parcel.readLong(); + } + + public static float readFloat(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readFloat(); + } + + public static double readDouble(Parcel parcel, int header) { + readExpectedSize(parcel, header, 8); + return parcel.readDouble(); + } + + public static String readString(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + String string = parcel.readString(); + parcel.setDataPosition(start + size); + return string; + } + + public static IBinder readBinder(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + IBinder binder = parcel.readStrongBinder(); + parcel.setDataPosition(start + size); + return binder; + } + + public static T readParcelable(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + T t = creator.createFromParcel(parcel); + parcel.setDataPosition(start + size); + return t; + } + + public static ArrayList readList(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.readArrayList(classLoader); + parcel.setDataPosition(start + size); + return list; + } + + public static HashMap readMap(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + HashMap map = parcel.readHashMap(classLoader); + parcel.setDataPosition(start + size); + return map; + } + + public static ArrayList readParcelableList(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.createTypedArrayList(creator); + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readStringList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.createStringArrayList(); + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readIntegerList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readInt()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readLongList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readLong()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readFloatList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readFloat()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readDoubleList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readDouble()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readBooleanList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readInt() != 0); + } + parcel.setDataPosition(start + size); + return list; + } + + public static T[] readParcelableArray(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + T[] arr = parcel.createTypedArray(creator); + parcel.setDataPosition(start + size); + return arr; + } + + public static String[] readStringArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + String[] arr = parcel.createStringArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static byte[] readByteArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + byte[] arr = parcel.createByteArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static byte[][] readByteArrayArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + byte[][] arr = new byte[length][]; + for (int i = 0; i < length; i++) { + arr[i] = parcel.createByteArray(); + } + parcel.setDataPosition(start + size); + return arr; + } + + public static float[] readFloatArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + float[] arr = parcel.createFloatArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static int[] readIntArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int[] arr = parcel.createIntArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static Bundle readBundle(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + Bundle bundle = parcel.readBundle(classLoader); + parcel.setDataPosition(start + size); + return bundle; + } + + public static void skip(Parcel parcel, int header) { + int size = readSize(parcel, header); + parcel.setDataPosition(parcel.dataPosition() + size); + } + + public static class ReadException extends RuntimeException { + public ReadException(String message, Parcel parcel) { + super(message); + } + } +} \ No newline at end of file diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java new file mode 100644 index 00000000..b2f96116 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java @@ -0,0 +1,384 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; +import java.util.Map; + +@SuppressWarnings("MagicNumber") +public final class SafeParcelWriter { + + private SafeParcelWriter() { + } + + private static void writeHeader(Parcel parcel, int fieldId, int size) { + if (size >= 0xFFFF) { + parcel.writeInt(0xFFFF0000 | fieldId); + parcel.writeInt(size); + } else { + parcel.writeInt(size << 16 | fieldId); + } + } + + @Deprecated + public static int writeStart(Parcel parcel) { + return writeObjectHeader(parcel); + } + + public static int writeObjectHeader(Parcel parcel) { + writeHeader(parcel, SafeParcelable.SAFE_PARCEL_OBJECT_MAGIC, 0xFFFF); + return parcel.dataPosition(); + } + + private static int writeObjectHeader(Parcel parcel, int fieldId) { + writeHeader(parcel, fieldId, 0xFFFF); + return parcel.dataPosition(); + } + + @Deprecated + public static void writeEnd(Parcel parcel, int start) { + finishObjectHeader(parcel, start); + } + + public static void finishObjectHeader(Parcel parcel, int start) { + int end = parcel.dataPosition(); + int length = end - start; + parcel.setDataPosition(start - 4); + parcel.writeInt(length); + parcel.setDataPosition(end); + } + + public static void write(Parcel parcel, int fieldId, Boolean val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val ? 1 : 0); + } + + public static void write(Parcel parcel, int fieldId, Byte val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Short val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Integer val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Long val) { + if (val == null) return; + writeHeader(parcel, fieldId, 8); + parcel.writeLong(val); + } + + public static void write(Parcel parcel, int fieldId, Float val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeFloat(val); + } + + public static void write(Parcel parcel, int fieldId, Double val) { + if (val == null) return; + writeHeader(parcel, fieldId, 8); + parcel.writeDouble(val); + } + + public static void write(Parcel parcel, int fieldId, String val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeString(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Parcelable val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + val.writeToParcel(parcel, flags); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Bundle val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeBundle(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, byte[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeByteArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, byte[][] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.length); + for (byte[] arr : val) { + parcel.writeByteArray(arr); + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, float[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeFloatArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, int[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeIntArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, String[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStringArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void writeStringList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStringList(val); + finishObjectHeader(parcel, start); + } + } + + public static void writeIntegerList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Integer i : val) { + parcel.writeInt(i); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeLongList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Long l : val) { + parcel.writeLong(l); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeFloatList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Float f : val) { + parcel.writeFloat(f); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeDoubleList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Double d : val) { + parcel.writeDouble(d); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeBooleanList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Boolean b : val) { + parcel.writeInt(b ? 1 : 0); + } + finishObjectHeader(parcel, start); + } + } + + private static void writeArrayPart(Parcel parcel, T val, int flags) { + int before = parcel.dataPosition(); + parcel.writeInt(1); + int start = parcel.dataPosition(); + val.writeToParcel(parcel, flags); + int end = parcel.dataPosition(); + parcel.setDataPosition(before); + parcel.writeInt(end - start); + parcel.setDataPosition(end); + } + + public static void write(Parcel parcel, int fieldId, T[] val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.length); + for (T t : val) { + if (t == null) { + parcel.writeInt(0); + } else { + writeArrayPart(parcel, t, flags); + } + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, List val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (T t : val) { + if (t == null) { + parcel.writeInt(0); + } else { + writeArrayPart(parcel, t, flags); + } + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Parcel val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.appendFrom(val, 0, val.dataSize()); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeList(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Map val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeMap(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, IBinder val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStrongBinder(val); + finishObjectHeader(parcel, start); + } + } + +} \ No newline at end of file diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java new file mode 100644 index 00000000..0d8d1cc0 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcelable; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public interface SafeParcelable extends Parcelable { + int SAFE_PARCEL_OBJECT_MAGIC = 0x4F45; + + @Target(ElementType.TYPE) + @interface Class { + } + + @Target(ElementType.CONSTRUCTOR) + @interface Constructor { + + } + + @Target(ElementType.PARAMETER) + @interface Param { + int value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface Field { + int value(); + + boolean mayNull() default false; + + java.lang.Class subClass() default SafeParcelable.class; + + boolean useValueParcel() default false; + + boolean useDirectList() default false; + + long versionCode() default -1; + + String defaultValue() default ""; + + String type() default ""; + + String getterName() default ""; + + String getter() default ""; + } +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java new file mode 100644 index 00000000..6afaa9c6 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcel; +import android.os.Parcelable; + +public interface SafeParcelableCreatorAndWriter extends Parcelable.Creator { + void writeToParcel(T object, Parcel parcel, int flags); +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java new file mode 100644 index 00000000..66b8b4d5 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcel; +import android.os.Parcelable; + +public class SafeParcelableSerializer { + public static T deserializeFromBytes(byte[] bytes, Parcelable.Creator tCreator) { + if (bytes == null) return null; + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(bytes, 0, bytes.length); + parcel.setDataPosition(0); + T parcelable = tCreator.createFromParcel(parcel); + parcel.recycle(); + return parcelable; + } + + public static byte[] serializeToBytes(T parcelable) { + if (parcelable == null) return null; + Parcel parcel = Parcel.obtain(); + parcelable.writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + return bytes; + } +} diff --git a/play-services-basement/src/main/java/org/microg/gms/common/Hide.java b/play-services-basement/src/main/java/org/microg/gms/common/Hide.java new file mode 100644 index 00000000..e15414b5 --- /dev/null +++ b/play-services-basement/src/main/java/org/microg/gms/common/Hide.java @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.common; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Hide the class, method or field from the public API. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface Hide { +} diff --git a/play-services-basement/src/main/java/org/microg/gms/common/PublicApi.java b/play-services-basement/src/main/java/org/microg/gms/common/PublicApi.java index 92663979..a7fb9b44 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/PublicApi.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/PublicApi.java @@ -43,6 +43,8 @@ public @interface PublicApi { * marked as public api. * * @return true if the method or field is not part of the public api + * @deprecated use {@link Hide} instead */ + @Deprecated boolean exclude() default false; } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java index d53f38dd..b4bea3f8 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java @@ -17,6 +17,7 @@ package org.microg.gms.auth.login; import android.annotation.SuppressLint; +import android.app.Activity; import android.content.res.Configuration; import android.os.Bundle; import android.util.DisplayMetrics; @@ -25,11 +26,10 @@ import android.widget.Button; import android.widget.TextView; import androidx.annotation.StringRes; -import androidx.appcompat.app.AppCompatActivity; import com.mgoogle.android.gms.R; -public abstract class AssistantActivity extends AppCompatActivity { +public abstract class AssistantActivity extends Activity { private static final int TITLE_MIN_HEIGHT = 64; private static final double TITLE_WIDTH_FACTOR = (8.0 / 18.0); From e4a990c8bf83ac3ccebb152ab50f16995b6f41c7 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Fri, 22 Dec 2023 20:12:06 +0900 Subject: [PATCH 04/63] Specify targetPackage for intents from Authenticator Preferences https://github.com/microg/GmsCore/commit/b31072290a8bef20feeeba12ef31d9e29a22b63f --- .../src/main/res/xml/preferences_auth.xml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/play-services-core/src/main/res/xml/preferences_auth.xml b/play-services-core/src/main/res/xml/preferences_auth.xml index 7ff14e56..0e549cb1 100755 --- a/play-services-core/src/main/res/xml/preferences_auth.xml +++ b/play-services-core/src/main/res/xml/preferences_auth.xml @@ -18,16 +18,22 @@ - + - + - + From 79d6cd0e709f55507203de4cf38db88947373f07 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sat, 23 Dec 2023 04:27:35 +0900 Subject: [PATCH 05/63] Set basePackageName --- build.gradle | 6 + play-services-base-core/build.gradle | 2 + .../microg/gms/AbstractGmsServiceBroker.java | 6 + .../org/microg/gms/common/PackageUtils.java | 53 ++++--- .../microg/gms/common/PackageSpoofUtils.kt | 6 +- .../microg/mgms/settings/SettingsProvider.kt | 2 +- play-services-basement/build.gradle | 1 + .../org/microg/gms/auth/AuthConstants.java | 4 +- .../java/org/microg/gms/common/Constants.java | 4 + .../org/microg/gms/common/GmsService.java | 143 +++++++++++++++++- .../java/org/microg/gms/gcm/GcmConstants.java | 59 +++++++- play-services-core/build.gradle | 17 +++ .../microg-ui-tools/build.gradle | 2 + .../src/main/AndroidManifest.xml | 43 +++--- .../java/org/microg/gms/auth/AuthManager.java | 3 +- .../org/microg/gms/gservices/GServices.java | 8 +- .../gms/gservices/GServicesProvider.java | 4 +- .../src/main/res/xml/authenticator.xml | 2 +- .../src/main/res/xml/preferences_auth.xml | 12 +- .../src/main/res/xml/preferences_start.xml | 7 +- 20 files changed, 320 insertions(+), 64 deletions(-) diff --git a/build.gradle b/build.gradle index 3a471c38..b9e5cae4 100644 --- a/build.gradle +++ b/build.gradle @@ -4,9 +4,15 @@ */ buildscript { +<<<<<<< HEAD <<<<<<< HEAD ext.kotlinVersion = '1.8.20' ======= +======= + ext.applicationNamespace = "com.google.android.gms" + ext.basePackageName = "com.mgoogle" + +>>>>>>> 25329625 (Set basePackageName) ext.safeParcelVersion = '1.7.1' ext.kotlinVersion = '1.7.21' diff --git a/play-services-base-core/build.gradle b/play-services-base-core/build.gradle index 5a5dfa30..34288f21 100644 --- a/play-services-base-core/build.gradle +++ b/play-services-base-core/build.gradle @@ -23,6 +23,8 @@ android { defaultConfig { minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk + + buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" } sourceSets { diff --git a/play-services-base-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java b/play-services-base-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java index 9ff67ff8..071d2d8b 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java +++ b/play-services-base-core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java @@ -26,6 +26,7 @@ import com.google.android.gms.common.internal.IGmsCallbacks; import com.google.android.gms.common.internal.IGmsServiceBroker; import com.google.android.gms.common.internal.ValidateAccountRequest; +import org.microg.gms.base.core.BuildConfig; import org.microg.gms.common.GmsService; import java.util.EnumSet; @@ -69,8 +70,13 @@ public abstract class AbstractGmsServiceBroker extends IGmsServiceBroker.Stub { request.gmsVersion = gmsVersion; request.packageName = packageName; request.extras = extras; +<<<<<<< HEAD request.account = null; request.scopes = null; +======= + request.account = accountName == null ? null : new Account(accountName, BuildConfig.BASE_PACKAGE_NAME); + request.scopes = scopes == null ? null : scopesFromStringArray(scopes); +>>>>>>> 25329625 (Set basePackageName) getService(callback, request); } diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java index 787d2e11..81374609 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java @@ -16,6 +16,13 @@ package org.microg.gms.common; +<<<<<<< HEAD +======= +import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; +import static org.microg.gms.common.Constants.GMS_PACKAGE_SIGNATURE_SHA1; + +import android.annotation.SuppressLint; +>>>>>>> 25329625 (Set basePackageName) import android.app.ActivityManager; import android.app.Application; import android.app.PendingIntent; @@ -28,6 +35,8 @@ import android.util.Log; import androidx.annotation.Nullable; +import org.microg.gms.base.core.BuildConfig; + import java.lang.reflect.Method; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -35,6 +44,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; @@ -48,16 +58,18 @@ public class PackageUtils { private static final String GOOGLE_APP_KEY = "24bb24c05e47e0aefa68a58a766179d9b613a600"; private static final String[] GOOGLE_PRIMARY_KEYS = {GOOGLE_PLATFORM_KEY, GOOGLE_PLATFORM_KEY_2, GOOGLE_APP_KEY}; - private static final Map KNOWN_GOOGLE_PACKAGES; + public static final Map KNOWN_GOOGLE_PACKAGES; static { KNOWN_GOOGLE_PACKAGES = new HashMap<>(); + KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.youtube", "24bb24c05e47e0aefa68a58a766179d9b613a600"); KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.youtube.music", "afb0fed5eeaebdd86f56a97742f4b6b33ef59875"); KNOWN_GOOGLE_PACKAGES.put("com.google.android.youtube", "24bb24c05e47e0aefa68a58a766179d9b613a600"); } public static boolean isGooglePackage(Context context, String packageName) { String signatureDigest = firstSignatureDigest(context, packageName); + packageName = PackageSpoofUtils.spoofPackageName(context.getPackageManager(), packageName); return isGooglePackage(packageName, signatureDigest); } @@ -74,7 +86,7 @@ public class PackageUtils { if (signatureDigest == null) return false; if (Arrays.asList(GOOGLE_PRIMARY_KEYS).contains(signatureDigest)) return true; if (!KNOWN_GOOGLE_PACKAGES.containsKey(packageName)) return false; - return KNOWN_GOOGLE_PACKAGES.get(packageName).equals(signatureDigest); + return Objects.equals(KNOWN_GOOGLE_PACKAGES.get(packageName), signatureDigest); } public static void assertExtendedAccess(Context context) { @@ -84,14 +96,14 @@ public class PackageUtils { public static boolean callerHasExtendedAccess(Context context) { String[] packagesForUid = context.getPackageManager().getPackagesForUid(Binder.getCallingUid()); - if (packagesForUid != null && packagesForUid.length != 0) { + if (packagesForUid != null) { for (String packageName : packagesForUid) { packageName = PackageSpoofUtils.spoofPackageName(context.getPackageManager(), packageName); if (isGooglePackage(context, packageName) || GMS_PACKAGE_NAME.equals(packageName)) return true; } } - return context.checkCallingPermission("org.mgoogle.gms.EXTENDED_ACCESS") == PackageManager.PERMISSION_GRANTED; + return context.checkCallingPermission(BuildConfig.BASE_PACKAGE_NAME + ".gms.EXTENDED_ACCESS") == PackageManager.PERMISSION_GRANTED; } public static void checkPackageUid(Context context, String packageName, int callingUid) { @@ -121,9 +133,9 @@ public class PackageUtils { try { info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); } catch (PackageManager.NameNotFoundException e) { - return null; + return KNOWN_GOOGLE_PACKAGES.get(packageName); } - if (info != null && info.signatures != null && info.signatures.length > 0) { + if (info != null && info.signatures != null) { for (Signature sig : info.signatures) { String digest = sha1sum(sig.toByteArray()); if (digest != null) { @@ -148,7 +160,7 @@ public class PackageUtils { } catch (PackageManager.NameNotFoundException e) { return null; } - if (info != null && info.signatures != null && info.signatures.length > 0) { + if (info != null && info.signatures != null) { for (Signature sig : info.signatures) { byte[] digest = sha1bytes(sig.toByteArray()); if (digest != null) { @@ -179,10 +191,7 @@ public class PackageUtils { } catch (final NoSuchAlgorithmException e) { return null; } - if (md != null) { - return md.digest(bytes); - } - return null; + return md.digest(bytes); } @Nullable @@ -253,7 +262,6 @@ public class PackageUtils { } @Nullable - @Deprecated public static String packageFromProcessId(Context context, int pid) { ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (manager == null) return null; @@ -278,7 +286,6 @@ public class PackageUtils { return null; } - @SuppressWarnings("deprecation") public static String packageFromPendingIntent(PendingIntent pi) { if (pi == null) return null; return pi.getCreatorPackage(); @@ -288,8 +295,13 @@ public class PackageUtils { if (android.os.Build.VERSION.SDK_INT >= 28) return Application.getProcessName(); try { + @SuppressLint("PrivateApi") Class activityThread = Class.forName("android.app.ActivityThread"); String methodName = "currentProcessName"; +<<<<<<< HEAD +======= + @SuppressLint("DiscouragedPrivateApi") +>>>>>>> 25329625 (Set basePackageName) Method getProcessName = activityThread.getDeclaredMethod(methodName); return (String) getProcessName.invoke(null); } catch (Exception e) { @@ -334,17 +346,12 @@ public class PackageUtils { } catch (final NoSuchAlgorithmException e) { return null; } - if (md != null) { - bytes = md.digest(bytes); - if (bytes != null) { - StringBuilder sb = new StringBuilder(2 * bytes.length); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } + bytes = md.digest(bytes); + StringBuilder sb = new StringBuilder(2 * bytes.length); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); } - return null; + return sb.toString(); } public static int versionCode(Context context, String packageName) { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt index 94ec088d..0f18d221 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt @@ -4,15 +4,17 @@ import android.content.pm.PackageManager import android.os.Bundle import android.util.Log +import org.microg.gms.base.core.BuildConfig + /** * utilities to spoof package information */ internal object PackageSpoofUtils { private const val TAG = "SpoofUtils" private const val META_SPOOF_PACKAGE_NAME = - "com.mgoogle.android.gms.SPOOFED_PACKAGE_NAME" + "${BuildConfig.BASE_PACKAGE_NAME}.android.gms.SPOOFED_PACKAGE_NAME" private const val META_SPOOF_PACKAGE_SIGNATURE = - "com.mgoogle.android.gms.SPOOFED_PACKAGE_SIGNATURE" + "${BuildConfig.BASE_PACKAGE_NAME}.android.gms.SPOOFED_PACKAGE_SIGNATURE" private val spoofedPackageNameCache = HashMap() private val spoofedPackageSignatureCache = HashMap() diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt index ba7e2e25..c24c3cfa 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt @@ -99,7 +99,7 @@ class SettingsProvider : ContentProvider() { CheckIn.VERSION_INFO -> checkInPrefs.getString(key, "") ?: "" CheckIn.DEVICE_DATA_VERSION_INFO -> checkInPrefs.getString(key, "") ?: "" CheckIn.BRAND_SPOOF -> getSettingsBoolean(key, false) - CheckIn.HIDE_LAUNCHER_ICON -> getSettingsBoolean(key, false) + CheckIn.HIDE_LAUNCHER_ICON -> getSettingsBoolean(key, true) else -> throw IllegalArgumentException() } } diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index bbc86b99..7ec7d753 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -39,6 +39,7 @@ android { minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk buildConfigField "int", "VERSION_CODE", "$appVersionCode" + buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" } buildFeatures { diff --git a/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java b/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java index 774f5756..21a5e68a 100644 --- a/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java +++ b/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java @@ -16,11 +16,13 @@ package org.microg.gms.auth; +import org.microg.gms.basement.BuildConfig; + public class AuthConstants { public static final String SCOPE_GET_ACCOUNT_ID = "^^_account_id_^^"; public static final String PROVIDER_METHOD_GET_ACCOUNTS = "get_accounts"; public static final String PROVIDER_METHOD_CLEAR_PASSWORD = "clear_password"; public static final String PROVIDER_EXTRA_CLEAR_PASSWORD = "clear_password"; public static final String PROVIDER_EXTRA_ACCOUNTS = "accounts"; - public static final String DEFAULT_ACCOUNT_TYPE = "com.mgoogle"; + public static final String DEFAULT_ACCOUNT_TYPE = BuildConfig.BASE_PACKAGE_NAME; } diff --git a/play-services-basement/src/main/java/org/microg/gms/common/Constants.java b/play-services-basement/src/main/java/org/microg/gms/common/Constants.java index 077bdbde..fd31e054 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/Constants.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/Constants.java @@ -21,6 +21,10 @@ import org.microg.gms.basement.BuildConfig; public class Constants { public static final int GMS_VERSION_CODE = (BuildConfig.VERSION_CODE / 1000) * 1000; public static final String GMS_PACKAGE_NAME = "com.google.android.gms"; +<<<<<<< HEAD +======= + public static final String GSF_PACKAGE_NAME = BuildConfig.BASE_PACKAGE_NAME + ".android.gsf"; +>>>>>>> 25329625 (Set basePackageName) public static final String GMS_PACKAGE_SIGNATURE_SHA1 = "38918a453d07199354f8b19af05ec6562ced5788"; public static final String GMS_SECONDARY_PACKAGE_SIGNATURE_SHA1 = "bd32424203e0fb25f36b57e5aa356f9bdd1da998"; 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 739bc259..375e4de2 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 @@ -19,15 +19,156 @@ package org.microg.gms.common; public enum GmsService { UNKNOWN(-2), ANY(-1), + GAMES(1, "com.google.android.gms.games.service.START", "com.google.android.gms.games.internal.connect.service.START"), + PLUS(2, "com.google.android.gms.plus.service.START", "com.google.android.gms.plus.service.internal.START"), + PANORAMA(3, "com.google.android.gms.panorama.service.START"), + WALLET(4, "com.google.android.gms.wallet.service.BIND"), PEOPLE(5, "com.google.android.gms.people.service.START"), + LOCATION(6), + APPSTATE(7, "com.google.android.gms.appstate.service.START"), + ADREQUEST(8, "com.google.android.gms.ads.service.START"), ACCOUNT(9, "com.google.android.gms.accounts.ACCOUNT_SERVICE"), + CAST(10, "com.google.android.gms.cast.service.BIND_CAST_DEVICE_CONTROLLER_SERVICE"), + DRIVE(11, "com.google.android.gms.drive.ApiService.START"), ADDRESS(12, "com.google.android.gms.identity.service.BIND"), + CAR(13, "com.google.android.gms.car.service.START"), + WEARABLE(14, "com.google.android.gms.wearable.BIND"), AUTH_PROXY(16, "com.google.android.gms.auth.service.START"), + FITNESS(17, "com.google.android.gms.fitness.GoogleFitnessService.START"), + REMINDERS(18, "com.google.android.gms.reminders.service.START"), + LIGHTWEIGHT_INDEX(19, "com.google.android.gms.icing.LIGHTWEIGHT_INDEX_SERVICE"), + DEVICE_CONNECTION(20, "com.google.android.gms.deviceconnection.service.START"), INDEX(21, "com.google.android.gms.icing.INDEX_SERVICE"), + LOCATION_REPORTING(22, "com.google.android.gms.location.reporting.service.START", "com.google.android.location.reporting.service.START"), + LOCATION_MANAGER(23, "com.google.android.location.internal.GoogleLocationManagerService.START"), + PLAY_LOG(24, "com.google.android.gms.playlog.service.START"), + DROIDGUARD(25, "com.google.android.gms.droidguard.service.START"), + LOCKBOX(26, "com.google.android.gms.lockbox.service.START"), + CAST_MIRRORING(27, "com.google.android.gms.cast_mirroring.service.START"), + NETWORK_QUALITY(28, "com.google.android.gms.mdm.services.START"), + FEEDBACK(29, "com.google.android.gms.feedback.internal.IFeedbackService"), + SEARCH_ADMINISTRATION(30), + PHOTO_AUTO_BACKUP(31, "com.google.android.gms.photos.autobackup.service.START"), + SEARCH_QUERIES(32), + SEARCH_GLOBAL(33), + UDC(35, "com.google.android.gms.udc.service.START"), + SEARCH_CORPORA(36), + DEVICE_MANAGER(37, "com.google.android.gms.mdm.services.DeviceManagerApiService.START"), + PSEUDONYMOUS_ID(38, "com.google.android.gms.pseudonymous.service.START"), COMMON(39, "com.google.android.gms.common.service.START"), + CLEARCUT_LOGGER(40, "com.google.android.gms.clearcut.service.START"), + USAGE_REPORTING(41, "com.google.android.gms.usagereporting.service.START"), + KIDS(42, "com.google.android.gms.kids.service.START"), + DOWNLOAD(43, "com.google.android.gms.common.download.START"), SIGN_IN(44, "com.google.android.gms.signin.service.START"), + SAFETY_NET_CLIENT(45, "com.google.android.gms.safetynet.service.START"), + GSERVICES(46, "com.google.android.gms.ads.gservice.START"), + CONTEXT_MANAGER(47, "com.google.android.contextmanager.service.ContextManagerService.START"), + AUDIO_MODEM(48, "com.google.android.gms.audiomodem.service.AudioModemService.START"), + NEARBY_SHARING(49, "com.google.android.gms.nearby.sharing.service.NearbySharingService.START"), + LIGHTWEIGHT_NETWORK_QUALITY(50, "com.google.android.gms.herrevad.services.LightweightNetworkQualityAndroidService.START"), + PHENOTYPE(51, "com.google.android.gms.phenotype.service.START"), + VOICE_UNLOCK(52, "com.google.android.gms.speech.service.START"), + NEARBY_CONNECTIONS(54, "com.google.android.gms.nearby.connection.service.START"), + FITNESS_SENSORS(55, "com.google.android.gms.fitness.SensorsApi"), + FITNESS_RECORDING(56, "com.google.android.gms.fitness.RecordingApi"), + FITNESS_HISTORY(57, "com.google.android.gms.fitness.HistoryApi"), + FITNESS_SESSIONS(58, "com.google.android.gms.fitness.SessionsApi"), + FITNESS_BLE(59, "com.google.android.gms.fitness.BleApi"), + FITNESS_CONFIG(60, "com.google.android.gms.fitness.ConfigApi"), + FITNESS_INTERNAL(61, "com.google.android.gms.fitness.InternalApi"), + NEARBY_MESSAGES(62, "com.google.android.gms.nearby.messages.service.NearbyMessagesService.START"), + HELP(63, "com.google.android.gms.googlehelp.service.GoogleHelpService.START"), + CONFIG(64, "com.google.android.gms.config.START"), + GEODATA(65, "com.google.android.gms.location.places.GeoDataApi"), + SEARCH_IME(66), + PLACE_DETECTION(67, "com.google.android.gms.location.places.PlaceDetectionApi"), CREDENTIALS(68, "com.google.android.gms.auth.api.credentials.service.START"), + NEARBY_BOOTSTRAP(69, "com.google.android.gms.nearby.bootstrap.service.NearbyBootstrapService.START"), + PLUS_INTERNAL(70), + SOURCE_DEVICE(75, "com.google.android.gms.smartdevice.d2d.SourceDeviceService.START"), + TARGET_DEVICE(76, "com.google.android.gms.smartdevice.d2d.TargetDeviceService.START"), + APP_INVITE(77, "com.google.android.gms.appinvite.service.START"), + TAP_AND_PAY(79, "com.google.android.gms.tapandpay.service.BIND"), + CHROME_SYNC(80, "com.google.android.gms.chromesync.service.START"), + ACCOUNTS(81, "com.google.android.gms.smartdevice.setup.accounts.AccountsService.START"), + CAST_REMOTE_DISPLAY(83, "com.google.android.gms.cast.remote_display.service.START"), + TRUST_AGENT(85, "com.google.android.gms.trustagent.StateApi.START"), + AUTH_SIGN_IN(91, "com.google.android.gms.auth.api.signin.service.START"), + MEASUREMENT(93, "com.google.android.gms.measurement.START"), + FREIGHTER(98, "com.google.android.gms.freighter.service.START"), + GUNS(110, "com.google.android.gms.notifications.service.START"), + BLE(111, "com.google.android.gms.beacon.internal.IBleService.START"), + FIREBASE_AUTH(112, "com.google.firebase.auth.api.gms.service.START"), + APP_INDEXING(113), + GASS(116, "com.google.android.gms.gass.START"), + WORK_ACCOUNT(120), + INSTANT_APPS(121, "com.google.android.gms.instantapps.START"), + CAST_FIRSTPATY(122, "com.google.android.gms.cast.firstparty.START"), + AD_CACHE(123, "com.google.android.gms.ads.service.CACHE"), + SMS_RETRIEVER(126, "com.google.android.gms.auth.api.phone.service.SmsRetrieverApiService.START"), + CRYPT_AUTH(129, "com.google.android.gms.auth.cryptauth.cryptauthservice.START"), + DYNAMIC_LINKS(131, "com.google.firebase.dynamiclinks.service.START"), + FONTS(132, "com.google.android.gms.fonts.service.START"), + ROMANESCO(135, "com.google.android.gms.romanesco.service.START"), + TRAINER(139, "com.google.android.gms.learning.trainer.START"), + FIDO2_REGULAR(148, "com.google.android.gms.fido.fido2.regular.START"), + FIDO2_PRIVILEGED(149, "com.google.android.gms.fido.fido2.privileged.START"), + DATA_DOWNLOAD(152, "com.google.android.mdd.service.START"), + ACCOUNT_DATA(153, "com.google.android.gms.auth.account.data.service.START"), + CONSTELLATION(155, "com.google.android.gms.constellation.service.START"), + AUDIT(154, "com.google.android.gms.audit.service.START"), + SYSTEM_UPDATE(157, "com.google.android.gms.update.START_API_SERVICE"), + MOBSTORE(160, "com.google.android.mobstore.service.START"), + USER_LOCATION(163, "com.google.android.gms.userlocation.service.START"), + AD_HTTP(166, "com.google.android.gms.ads.service.HTTP"), + LANGUAGE_PROFILE(167, "com.google.android.gms.languageprofile.service.START"), + MDNS(168, "com.google.android.gms.mdns.service.START"), + 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"), + SYSTEM_UPDATE_SINGLE_UESR(192, "com.google.android.gms.update.START_SINGLE_USER_API_SERVICE"), + APP_USAGE(193, "com.google.android.gms.appusage.service.START"), + NEARBY_SHARING_2(194, "com.google.android.gms.nearby.sharing.START_SERVICE"), + AD_CONSENT_LOOKUP(195, "com.google.android.gms.ads.service.CONSENT_LOOKUP"), + CREDENTIAL_MANAGER(196, "com.google.android.gms.credential.manager.service.firstparty.START"), + PHONE_INTERNAL(197, "com.google.android.gms.auth.api.phone.service.InternalService.START"), + PAY(198, "com.google.android.gms.pay.service.BIND"), + ASTERISM(199, "com.google.android.gms.asterism.service.START"), + MODULE_RESTORE(201, "com.google.android.gms.backup.GMS_MODULE_RESTORE"), + FACS_CACHE(202, "com.google.android.gms.facs.cache.service.START"), + RECAPTCHA(205, "com.google.android.gms.recaptcha.service.START"), + CONTACT_SYNC(208, "com.google.android.gms.people.contactssync.service.START"), IDENTITY_SIGN_IN(212, "com.google.android.gms.auth.api.identity.service.signin.START"), + CREDENTIAL_STORE(214, "com.google.android.gms.fido.credentialstore.internal_service.START"), + MDI_SYNC(215, "com.google.android.gms.mdisync.service.START"), + EVENT_ATTESTATION(216, "com.google.android.gms.ads.identifier.service.EVENT_ATTESTATION"), + SCHEDULER(218, "com.google.android.gms.scheduler.ACTION_PROXY_SCHEDULE"), + AUTHORIZATION(219, "com.google.android.gms.auth.api.identity.service.authorization.START"), + FACS_SYNC(220, "com.google.android.gms.facs.internal.service.START"), + AUTH_CONFIG_SYNC(221, "com.google.android.gms.auth.config.service.START"), + CREDENTIAL_SAVING(223, "com.google.android.gms.auth.api.identity.service.credentialsaving.START"), + GOOGLE_AUTH(224, "com.google.android.gms.auth.account.authapi.START"), + ENTERPRISE_LOADER(225, "com.google.android.gms.enterprise.loader.service.START"), + THUNDERBIRD(226, "com.google.android.gms.thunderbird.service.START"), + NEARBY_EXPOSURE(236, "com.google.android.gms.nearby.exposurenotification.START"), + GMS_COMPLIANCE(257, "com.google.android.gms.gmscompliance.service.START"), + FIDO_SOURCE_DEVICE(262, "com.google.android.gms.fido.sourcedevice.service.START"), + FAST_PAIR(265, "com.google.android.gms.nearby.fastpair.START"), + MATCHSTICK_LIGHTER(268, "com.google.android.gms.matchstick.lighter.service.START"), + FIDO_TARGET_DEVICE_INTERNAL(269, "com.google.android.gms.fido.targetdevice.internal_service.START"), + TELEMETRY(270, "com.google.android.gms.common.telemetry.service.START"), + SECOND_DEVICE_AUTH(275, "com.google.android.gms.setup.auth.SecondDeviceAuth.START"), + LOCATION_SHARING_REPORTER(277, "com.google.android.gms.locationsharingreporter.service.START"), + OCR(279, "com.google.android.gms.ocr.service.START"), + POTOKENS(285, "com.google.android.gms.potokens.service.START"), + OCR_INTERNAL(281, "com.google.android.gms.ocr.service.internal.START"), + IN_APP_REACH(315, "com.google.android.gms.inappreach.service.START"), + APP_ERRORS(334, "com.google.android.gms.apperrors.service.START_APP_ERROR"), ; public final int SERVICE_ID; @@ -55,4 +196,4 @@ public enum GmsService { if (this != UNKNOWN) return toString(); return "UNKNOWN(" + serviceId + ")"; } -} \ No newline at end of file +} diff --git a/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java b/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java index d96fe90a..00117d4e 100644 --- a/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java +++ b/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java @@ -16,29 +16,80 @@ package org.microg.gms.gcm; +import org.microg.gms.basement.BuildConfig; + public final class GcmConstants { - public static final String ACTION_C2DM_RECEIVE = "com.mgoogle.android.c2dm.intent.RECEIVE"; - public static final String ACTION_C2DM_REGISTER = "com.mgoogle.android.c2dm.intent.REGISTER"; - public static final String ACTION_C2DM_REGISTRATION = "com.mgoogle.android.c2dm.intent.REGISTRATION"; - public static final String ACTION_C2DM_UNREGISTER = "com.mgoogle.android.c2dm.intent.UNREGISTER"; + public static final String ACTION_C2DM_RECEIVE = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.intent.RECEIVE"; + public static final String ACTION_C2DM_REGISTER = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.intent.REGISTER"; + public static final String ACTION_C2DM_REGISTRATION = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.intent.REGISTRATION"; + public static final String ACTION_C2DM_UNREGISTER = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.intent.UNREGISTER"; + public static final String ACTION_GCM_SEND = BuildConfig.BASE_PACKAGE_NAME + ".android.gcm.intent.SEND"; + public static final String ACTION_NOTIFICATION_OPEN = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.NOTIFICATION_OPEN"; + public static final String ACTION_NOTIFICATION_DISMISS = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.NOTIFICATION_DISMISS"; + public static final String ACTION_SCHEDULE = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.ACTION_SCHEDULE"; + public static final String ACTION_TASK_READY = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.ACTION_TASK_READY"; + public static final String ACTION_TASK_INITIALZE = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.SERVICE_ACTION_INITIALIZE"; + public static final String ACTION_INSTANCE_ID = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.iid.InstanceID"; public static final String EXTRA_APP = "app"; public static final String EXTRA_APP_OVERRIDE = "org.microg.gms.gcm.APP_OVERRIDE"; + public static final String EXTRA_APP_ID = "appid"; + public static final String EXTRA_APP_VERSION_CODE = "app_ver"; public static final String EXTRA_APP_VERSION_NAME = "app_ver_name"; + public static final String EXTRA_CLIENT_VERSION = "cliv"; + public static final String EXTRA_COMPONENT = "component"; public static final String EXTRA_COLLAPSE_KEY = "collapse_key"; + public static final String EXTRA_DELAY = "google.delay"; public static final String EXTRA_DELETE = "delete"; public static final String EXTRA_ERROR = "error"; public static final String EXTRA_FROM = "from"; + public static final String EXTRA_GSF_INTENT = "GSF"; + public static final String EXTRA_GMS_VERSION = "gmsv"; + public static final String EXTRA_IS_MESSENGER2 = "messenger2"; public static final String EXTRA_KID = "kid"; public static final String EXTRA_MESSENGER = "google.messenger"; + public static final String EXTRA_MESSAGE_TYPE = "message_type"; public static final String EXTRA_MESSAGE_ID = "google.message_id"; + public static final String EXTRA_OS_VERSION = "osv"; + public static final String EXTRA_PENDING_INTENT = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.gcm.PENDING_INTENT"; + public static final String EXTRA_PUBLIC_KEY = "pub2"; + public static final String EXTRA_RAWDATA = "rawData"; + public static final String EXTRA_RAWDATA_BASE64 = "gcm.rawData64"; public static final String EXTRA_REGISTRATION_ID = "registration_id"; public static final String EXTRA_RETRY_AFTER = "Retry-After"; + public static final String EXTRA_SCHEDULER_ACTION = "scheduler_action"; + public static final String EXTRA_SCOPE = "scope"; public static final String EXTRA_SENDER = "sender"; + public static final String EXTRA_SENDER_LEGACY = "legacy.sender"; public static final String EXTRA_SEND_TO = "google.to"; public static final String EXTRA_SEND_FROM = "google.from"; + public static final String EXTRA_SIGNATURE = "sig"; + public static final String EXTRA_SUBSCIPTION = "subscription"; + public static final String EXTRA_SUBTYPE = "subtype"; + public static final String EXTRA_USE_GSF = "useGsf"; + public static final String EXTRA_TAG = "tag"; + public static final String EXTRA_TOPIC = "gcm.topic"; public static final String EXTRA_TTL = "google.ttl"; public static final String EXTRA_UNREGISTERED = "unregistered"; + public static final String MESSAGE_TYPE_GCM = "gcm"; + public static final String MESSAGE_TYPE_DELETED_MESSAGE = "deleted_message"; + public static final String MESSAGE_TYPE_SEND_ERROR = "send_error"; + public static final String MESSAGE_TYPE_SEND_EVENT = "send_event"; + + public static final String SCHEDULER_ACTION_CANCEL = "CANCEL_TASK"; + public static final String SCHEDULER_ACTION_CANCEL_ALL = "CANCEL_ALL"; + public static final String SCHEDULER_ACTION_SCHEDULE = "SCHEDULE_TASK"; + + public static final String PERMISSION_GTALK = BuildConfig.BASE_PACKAGE_NAME + ".android.gtalkservice.permission.GTALK_SERVICE"; + public static final String PERMISSION_NETWORK_TASK = BuildConfig.BASE_PACKAGE_NAME + ".android.gms.permission.BIND_NETWORK_TASK_SERVICE"; + public static final String PERMISSION_RECEIVE = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.permission.RECEIVE"; + public static final String PERMISSION_SEND = BuildConfig.BASE_PACKAGE_NAME + ".android.c2dm.permission.SEND"; + public static final String ERROR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE"; + + public static final String INSTANCE_ID_SCOPE_GCM = "GCM"; + + public static final String GCMID_INSTANCE_ID = "google.com/iid"; + public static final String GCMID_REFRESH = "gcm.googleapis.com/refresh"; } diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 2449e81f..1e97f468 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -47,6 +47,8 @@ dependencies { } android { + namespace "${basePackageName}.android.gms" + compileSdkVersion androidCompileSdk buildToolsVersion androidBuildVersionTools @@ -58,10 +60,25 @@ android { minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk + buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" + ndk { abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" } + buildConfigField "String", "APPLICATION_NAMESPACE", "\"${android.namespace}\"" + buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" + manifestPlaceholders = [ + basePackageName: "$basePackageName" + ] + + // provide package name to xml resources + resValue "string", "var_authenticator_account_type", "$basePackageName" + resValue "string", "var_contact_syncadapter_account_type", "$basePackageName" + resValue "string", "var_pref_auth_action_security_settings", "${basePackageName}.android.gms.accountsettings.SECURITY_SETTINGS" + resValue "string", "var_pref_auth_action_privacy_settings", "${basePackageName}.android.gms.accountsettings.PRIVACY_SETTINGS" + resValue "string", "var_pref_auth_action_preferences_settings", "${basePackageName}.android.gms.accountsettings.ACCOUNT_PREFERENCES_SETTINGS" + resValue "string", "var_pref_start_gms_package", "${basePackageName}.android.gms" } buildFeatures { diff --git a/play-services-core/microg-ui-tools/build.gradle b/play-services-core/microg-ui-tools/build.gradle index d9a5c2bb..9b1df7b8 100644 --- a/play-services-core/microg-ui-tools/build.gradle +++ b/play-services-core/microg-ui-tools/build.gradle @@ -23,6 +23,8 @@ android { defaultConfig { minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk + + buildConfigField "String", "GMS_APPLICATION_NAMESPACE", "\"$applicationNamespace\"" } compileOptions { diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 385085d1..a38f4479 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -18,36 +18,40 @@ xmlns:tools="http://schemas.android.com/tools"> >>>>>> 25329625 (Set basePackageName) android:label="@string/perm_c2dm_receive_label" android:permissionGroup="android.permission-group.NETWORK" android:protectionLevel="normal" /> @@ -67,11 +71,11 @@ - - - + + + - + @@ -94,7 +98,7 @@ android:networkSecurityConfig="@xml/network_security_config" tools:targetApi="r"> - + @@ -139,15 +143,16 @@ - + + @@ -158,8 +163,8 @@ android:process=":persistent" android:exported="true"> - - + + @@ -170,7 +175,7 @@ android:process=":persistent" android:exported="true"> - + @@ -236,7 +241,7 @@ - + + android:action="@string/var_pref_auth_action_security_settings" + android:targetPackage="@string/var_pref_start_gms_package" /> + android:action="@string/var_pref_auth_action_privacy_settings" + android:targetPackage="@string/var_pref_start_gms_package" /> + android:action="@string/var_pref_auth_action_preferences_settings" + android:targetPackage="@string/var_pref_start_gms_package" /> diff --git a/play-services-core/src/main/res/xml/preferences_start.xml b/play-services-core/src/main/res/xml/preferences_start.xml index 7893924b..5db2a913 100755 --- a/play-services-core/src/main/res/xml/preferences_start.xml +++ b/play-services-core/src/main/res/xml/preferences_start.xml @@ -22,8 +22,13 @@ android:title="@string/account_manager_title" android:summary="@string/manage_google_account_summary"> +======= + android:targetClass="org.microg.gms.auth.login.LoginActivity" + android:targetPackage="@string/var_pref_start_gms_package" /> +>>>>>>> 25329625 (Set basePackageName) + android:targetPackage="@string/var_pref_start_gms_package" /> Date: Sat, 23 Dec 2023 04:38:18 +0900 Subject: [PATCH 06/63] ProfileManager: Fix for old Android versions https://github.com/microg/GmsCore/commit/b3f5a33713df776f62ee47b43e37b2ede1548c04 --- .../kotlin/org/microg/gms/profile/Build.kt | 3 + .../org/microg/gms/profile/ProfileManager.kt | 117 ++++++++++++------ 2 files changed, 80 insertions(+), 40 deletions(-) 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 c0398c2b..564d8708 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 @@ -87,6 +87,9 @@ object Build { @JvmField var SECURITY_PATCH: String? = null + + @JvmField + var DEVICE_INITIAL_SDK_INT: Int = 0 } fun generateWebViewUserAgentString(original: String): String { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt index 0cbc69b8..8cb1afd2 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt @@ -72,43 +72,60 @@ object ProfileManager { PROFILE_AUTO -> false PROFILE_REAL -> false PROFILE_NATIVE -> true - else -> getProfileXml(context, profile)?.use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "profile" -> { - return@use it.getAttributeBooleanValue(null, "auto", false) + else -> { + val parser = getProfileXml(context, profile) + if (parser != null) { + try { + var next = parser.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (parser.name) { + "profile" -> { + return@runCatching parser.getAttributeBooleanValue(null, "auto", false) + } + } } + next = parser.next() } + } finally { + parser.close() } - next = it.next() + false + } else { + false } - } == true + } } }.getOrDefault(false) + fun getActiveProfileData(context: Context): Map = + getProfileData(context, getProfile(context), getRealData()) + private fun getProfileData(context: Context, profile: String, realData: Map): Map { try { if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return realData - val profileResId = getProfileResId(context, profile) - if (profileResId == 0) return realData + if (profile != PROFILE_USER && getProfileResId(context, profile) == 0) return realData val resultData = mutableMapOf() resultData.putAll(realData) - getProfileXml(context, profile)?.use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "data" -> { - val key = it.getAttributeValue(null, "key") - val value = it.getAttributeValue(null, "value") - resultData[key] = value - Log.d(TAG, "Overwrite from profile: $key = $value") + val parser = getProfileXml(context, profile) + if (parser != null) { + try { + var next = parser.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (parser.name) { + "data" -> { + val key = parser.getAttributeValue(null, "key") + val value = parser.getAttributeValue(null, "value") + resultData[key] = value + Log.d(TAG, "Overwrite from profile: $key = $value") + } } } + next = parser.next() } - next = it.next() + } finally { + parser.close() } } return resultData @@ -162,15 +179,20 @@ object ProfileManager { // From profile try { - getProfileXml(context, profile)?.use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "serial" -> return it.getAttributeValue(null, "template") + val parser = getProfileXml(context, profile) + if (parser != null) { + try { + var next = parser.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (parser.name) { + "serial" -> return parser.getAttributeValue(null, "template") + } } + next = parser.next() } - next = it.next() + } finally { + parser.close() } } } catch (e: Exception) { @@ -193,6 +215,7 @@ object ProfileManager { return serial } + @SuppressLint("BlockedPrivateApi") private fun getRealData(): Map = mutableMapOf( "Build.BOARD" to android.os.Build.BOARD, "Build.BOOTLOADER" to android.os.Build.BOOTLOADER, @@ -226,9 +249,15 @@ object ProfileManager { if (android.os.Build.VERSION.SDK_INT >= 23) { put("Build.VERSION.SECURITY_PATCH", android.os.Build.VERSION.SECURITY_PATCH) } + try { + val field = android.os.Build.VERSION::class.java.getDeclaredField("DEVICE_INITIAL_SDK_INT") + field.isAccessible = true + put("Build.VERSION.DEVICE_INITIAL_SDK_INT", field.getInt(null).toString()) + } catch (ignored: Exception) { + } } - private fun applyProfileData(profileData: Map) { + fun applyProfileData(profileData: Map) { fun applyStringField(key: String, valueSetter: (String) -> Unit) = profileData[key]?.let { valueSetter(it) } fun applyIntField(key: String, valueSetter: (Int) -> Unit) = profileData[key]?.toIntOrNull()?.let { valueSetter(it) } fun applyLongField(key: String, valueSetter: (Long) -> Unit) = profileData[key]?.toLongOrNull()?.let { valueSetter(it) } @@ -258,6 +287,7 @@ object ProfileManager { applyStringField("Build.VERSION.RELEASE") { Build.VERSION.RELEASE = it } applyStringField("Build.VERSION.SDK") { Build.VERSION.SDK = it } applyIntField("Build.VERSION.SDK_INT") { Build.VERSION.SDK_INT = it } + applyIntField("Build.VERSION.DEVICE_INITIAL_SDK_INT") { Build.VERSION.DEVICE_INITIAL_SDK_INT = it } if (android.os.Build.VERSION.SDK_INT >= 21) { Build.SUPPORTED_ABIS = profileData["Build.SUPPORTED_ABIS"]?.split(",")?.toTypedArray() ?: emptyArray() } else { @@ -285,19 +315,26 @@ object ProfileManager { fun getProfileName(context: Context, profile: String): String? = getProfileName { getProfileXml(context, profile) } - private fun getProfileName(parserCreator: () -> XmlResourceParser?): String? = parserCreator()?.use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "profile" -> { - return@use it.getAttributeValue(null, "name") + private fun getProfileName(parserCreator: () -> XmlResourceParser?): String? { + val parser = parserCreator() + if (parser != null) { + try { + var next = parser.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (parser.name) { + "profile" -> { + return parser.getAttributeValue(null, "name") + } + } } + next = parser.next() } + } finally { + parser.close() } - next = it.next() } - null + return null } fun setProfile(context: Context, profile: String?) { @@ -317,7 +354,7 @@ object ProfileManager { val profileName = getProfileName { FileXmlResourceParser(file) } ?: return false try { Log.d(TAG, "Importing user profile '$profileName'") - file.copyTo(getUserProfileFile(context)) + file.copyTo(getUserProfileFile(context), overwrite = true) if (activeProfile == PROFILE_USER) applyProfile(context, PROFILE_USER) return true } catch (e: Exception) { From 89ee97e3b944465616020bc2036a344ecab1be55 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sat, 23 Dec 2023 04:43:23 +0900 Subject: [PATCH 07/63] Location: Entire rewrite of the location subsystem https://github.com/microg/GmsCore/commit/f5ad796b827de2993153c4459e582c4c9037f7b6 --- .../android/gms/common/internal/GetServiceRequest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java index a203a99f..9df8735c 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java @@ -31,7 +31,7 @@ import java.util.Arrays; public class GetServiceRequest extends AutoSafeParcelable { @Field(1) - private final int versionCode = 4; + private final int versionCode = 6; @Field(2) public final int serviceId; @Field(3) @@ -47,6 +47,7 @@ public class GetServiceRequest extends AutoSafeParcelable { @Field(8) public Account account; @Field(9) + @Deprecated private long field9; @Field(10) public Feature[] defaultFeatures; @@ -59,7 +60,7 @@ public class GetServiceRequest extends AutoSafeParcelable { @Field(14) private boolean field14; @Field(15) - private String field15; + private String attributionTag; private GetServiceRequest() { serviceId = -1; From 10a29689c101c5f20f64bb0ee82ebbbf7ac0a3f5 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sat, 23 Dec 2023 05:12:29 +0900 Subject: [PATCH 08/63] Asorted fixes https://github.com/microg/GmsCore/commit/80c1d9cf61baeec0692ec9efd3233305cda3c016 --- .../org/microg/gms/common/PackageUtils.java | 2 +- .../org/microg/gms/utils/BinderUtils.kt | 6 +++--- .../gms/auth/AuthManagerServiceImpl.java | 21 +++++++++++++++++++ .../microg/gms/auth/appcert/AppCertService.kt | 2 +- .../auth/credentials/CredentialsService.kt | 2 +- .../microg/gms/auth/proxy/AuthProxyService.kt | 7 ++++++- 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java index 81374609..18220e82 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java @@ -230,7 +230,7 @@ public class PackageUtils { if (suggestedCallerPid > 0 && suggestedCallerPid != callingPid) { throw new SecurityException("suggested PID [" + suggestedCallerPid + "] and real calling PID [" + callingPid + "] mismatch!"); } - return getAndCheckPackage(context, suggestedPackageName, callingUid, Binder.getCallingPid()); + return getAndCheckPackage(context, suggestedPackageName, callingUid, callingPid); } @Nullable diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt index 79e8ff8f..226bb431 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt @@ -12,13 +12,13 @@ import android.util.Log private const val TAG = "BinderUtils" -fun IBinder.warnOnTransactionIssues(code: Int, reply: Parcel?, flags: Int, base: () -> Boolean): Boolean { +fun IBinder.warnOnTransactionIssues(code: Int, reply: Parcel?, flags: Int, tag: String = TAG, base: () -> Boolean): Boolean { if (base.invoke()) { if ((flags and Binder.FLAG_ONEWAY) > 0 && (reply?.dataSize() ?: 0) > 0) { - Log.w(TAG, "Method $code in $interfaceDescriptor is oneway, but returned data") + Log.w(tag, "Method $code in $interfaceDescriptor is oneway, but returned data") } return true } - Log.w(TAG, "Unknown method $code in $interfaceDescriptor, skipping") + Log.w(tag, "Unknown method $code in $interfaceDescriptor, skipping") return (flags and Binder.FLAG_ONEWAY) > 0 // Don't return false on oneway transaction to suppress warning } 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 5ddf9d28..58e26a13 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 @@ -48,6 +48,12 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +<<<<<<< HEAD +======= +import static android.accounts.AccountManager.*; +import static android.os.Build.VERSION.SDK_INT; + +>>>>>>> 6f98f5aa (Asorted fixes https://github.com/microg/GmsCore/commit/80c1d9cf61baeec0692ec9efd3233305cda3c016) public class AuthManagerServiceImpl extends IAuthManagerService.Stub { private static final String TAG = "GmsAuthManagerSvc"; @@ -169,8 +175,23 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override +<<<<<<< HEAD public Bundle requestGoogleAccountsAccess(String packageName) { Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")"); +======= + public Bundle requestGoogleAccountsAccess(String packageName) throws RemoteException { + PackageUtils.assertExtendedAccess(context); + if (SDK_INT >= 26) { + for (Account account : get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)) { + AccountManager.get(context).setAccountVisibility(account, packageName, VISIBILITY_VISIBLE); + } + Bundle res = new Bundle(); + res.putString("Error", "Ok"); + return res; + } else { + Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")"); + } +>>>>>>> 6f98f5aa (Asorted fixes https://github.com/microg/GmsCore/commit/80c1d9cf61baeec0692ec9efd3233305cda3c016) return null; } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt index e73017e2..9b14af49 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt @@ -38,5 +38,5 @@ class AppCertServiceImpl(private val context: Context) : IAppCertService.Stub() return runBlocking { manager.getSpatulaHeader(packageName) } } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt index adc0370b..4e933426 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt @@ -52,5 +52,5 @@ class CredentialsServiceImpl : ICredentialsService.Stub() { callbacks.onStatus(Status.SUCCESS) } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt index fe3d5e5a..f782fc7e 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt @@ -59,5 +59,10 @@ class AuthServiceImpl(private val context: Context, private val lifecycle: Lifec override fun getLifecycle(): Lifecycle = lifecycle +<<<<<<< HEAD override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } -} \ No newline at end of file +} +======= + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} +>>>>>>> 6f98f5aa (Asorted fixes https://github.com/microg/GmsCore/commit/80c1d9cf61baeec0692ec9efd3233305cda3c016) From 21e75291fee1e474e7a10492945dbaaa3add6aa2 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sat, 23 Dec 2023 05:21:03 +0900 Subject: [PATCH 09/63] AppCert: Update for latest API https://github.com/microg/GmsCore/commit/3a42d956abb017e35d30204c556afcc29cb8a636 --- .../common/internal/IGoogleCertificatesApi.aidl | 2 +- .../gms/common/GoogleCertificatesImpl.java | 15 +++++++++++---- .../org/microg/gms/auth/appcert/AppCertManager.kt | 11 ++++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl b/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl index da539f32..c75982bb 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl @@ -4,7 +4,7 @@ import com.google.android.gms.common.internal.GoogleCertificatesQuery; import com.google.android.gms.dynamic.IObjectWrapper; interface IGoogleCertificatesApi { - IObjectWrapper getGoogleCertficates(); + IObjectWrapper getGoogleCertificates(); IObjectWrapper getGoogleReleaseCertificates(); boolean isGoogleReleaseSigned(String packageName, IObjectWrapper certData); boolean isGoogleSigned(String packageName, IObjectWrapper certData); diff --git a/play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java b/play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java index 7ab30bc3..948361f0 100644 --- a/play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java @@ -17,11 +17,13 @@ package com.google.android.gms.common; import android.content.pm.PackageManager; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import androidx.annotation.Keep; +import com.google.android.gms.common.internal.CertData; import com.google.android.gms.common.internal.GoogleCertificatesQuery; import com.google.android.gms.common.internal.IGoogleCertificatesApi; import com.google.android.gms.dynamic.IObjectWrapper; @@ -29,20 +31,25 @@ import com.google.android.gms.dynamic.ObjectWrapper; import org.microg.gms.common.PackageUtils; +import java.util.Collections; +import java.util.Set; + @Keep public class GoogleCertificatesImpl extends IGoogleCertificatesApi.Stub { private static final String TAG = "GmsCertImpl"; + private Set googleCertificates = Collections.emptySet(); + private Set googleReleaseCertificates = Collections.emptySet(); @Override - public IObjectWrapper getGoogleCertficates() throws RemoteException { - Log.d(TAG, "unimplemented Method: getGoogleCertficates"); - return null; + public IObjectWrapper getGoogleCertificates() throws RemoteException { + Log.d(TAG, "unimplemented Method: getGoogleCertificates"); + return ObjectWrapper.wrap(googleCertificates.toArray(new IBinder[0])); } @Override public IObjectWrapper getGoogleReleaseCertificates() throws RemoteException { Log.d(TAG, "unimplemented Method: getGoogleReleaseCertificates"); - return null; + return ObjectWrapper.wrap(googleReleaseCertificates.toArray(new IBinder[0])); } @Override diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt index 3120a555..7ae44173 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt @@ -96,7 +96,11 @@ class AppCertManager(private val context: Context) { } override fun deliverError(error: VolleyError) { - Log.d(TAG, "Error: ${Base64.encodeToString(error.networkResponse.data, 2)}") + if (error.networkResponse != null) { + Log.d(TAG, "Error: ${Base64.encodeToString(error.networkResponse.data, 2)}") + } else { + Log.d(TAG, "Error: ${error.message}") + } deviceKeyCacheTime = 0 deferredResponse.complete(null) } @@ -149,14 +153,19 @@ class AppCertManager(private val context: Context) { ) } else { Log.d(TAG, "Using fallback spatula header based on Android ID") +<<<<<<< HEAD val androidId = CheckIn.getContentUri(context)?.let { getSettings(context, it, arrayOf(CheckIn.ANDROID_ID, CheckIn.SECURITY_TOKEN)) { cursor: Cursor -> cursor.getLong(0) } } +======= + val androidId = getSettings(context, CheckIn.getContentUri(context), arrayOf(CheckIn.ANDROID_ID)) { cursor: Cursor -> cursor.getLong(0) } +>>>>>>> dc5ec8f7 (AppCert: Update for latest API https://github.com/microg/GmsCore/commit/3a42d956abb017e35d30204c556afcc29cb8a636) SpatulaHeaderProto( packageInfo = SpatulaHeaderProto.PackageInfo(packageName, packageCertificateHash), deviceId = androidId ) + return null // TODO } Log.d(TAG, "Spatula Header: $proto") return Base64.encodeToString(proto.encode(), Base64.NO_WRAP) From 1b4ff54e593bc5d0e18c7a3bb3f95a3b6445f1a8 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 24 Dec 2023 01:30:27 +0900 Subject: [PATCH 10/63] Push: Add minor features https://github.com/microg/GmsCore/commit/23cb98075af50fdb62dc44906290290698e55ce3 --- .../src/main/java/org/microg/gms/gcm/McsService.java | 9 ++++++--- .../java/org/microg/gms/gcm/PushRegisterManager.java | 2 +- 2 files changed, 7 insertions(+), 4 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 a21d9137..7073b5fa 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 @@ -43,6 +43,7 @@ import android.os.Parcelable; import android.os.PowerManager; import android.os.SystemClock; import android.os.UserHandle; +import android.util.Base64; import android.util.Log; import androidx.annotation.Nullable; @@ -536,15 +537,17 @@ public class McsService extends Service implements Handler.Callback { intent.setAction(ACTION_C2DM_RECEIVE); intent.putExtra(EXTRA_FROM, msg.from); intent.putExtra(EXTRA_MESSAGE_ID, msg.id); - if (msg.persistent_id != null) { - intent.putExtra(EXTRA_MESSAGE_ID, msg.persistent_id); + if (msg.persistent_id != null) intent.putExtra(EXTRA_MESSAGE_ID, msg.persistent_id); + if (msg.token != null) intent.putExtra(EXTRA_COLLAPSE_KEY, msg.token); + if (msg.raw_data != null) { + intent.putExtra(EXTRA_RAWDATA_BASE64, Base64.encodeToString(msg.raw_data.toByteArray(), Base64.DEFAULT)); + intent.putExtra(EXTRA_RAWDATA, msg.raw_data.toByteArray()); } if (app.wakeForDelivery) { intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); } else { intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); } - if (msg.token != null) intent.putExtra(EXTRA_COLLAPSE_KEY, msg.token); for (AppData appData : msg.app_data) { intent.putExtra(appData.key, appData.value_); } diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java index 1dcfec44..bc93194e 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java @@ -147,7 +147,7 @@ public class PushRegisterManager { resultBundle.putString(EXTRA_REGISTRATION_ID, attachRequestId(response.token, requestId)); } } else { - if (!request.app.equals(response.deleted) && !request.app.equals(response.token)) { + if (!request.app.equals(response.deleted) && !request.app.equals(response.token) && !request.sender.equals(response.token)) { database.noteAppRegistrationError(request.app, response.responseText); resultBundle.putString(EXTRA_ERROR, attachRequestId(ERROR_SERVICE_NOT_AVAILABLE, requestId)); } else { From 08f46a1a49ac7d337cbee2da16b1763227edca94 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 24 Dec 2023 01:47:40 +0900 Subject: [PATCH 11/63] Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff --- .../microg/mgms/settings/SettingsProvider.kt | 9 ++-- .../java/org/microg/gms/gcm/McsService.java | 4 +- .../kotlin/org/microg/gms/gcm/GcmPrefs.kt | 49 +++++++++++++++++-- .../ui/PushNotificationAdvancedFragment.kt | 12 ++--- .../main/res/xml/preferences_gcm_advanced.xml | 4 ++ 5 files changed, 62 insertions(+), 16 deletions(-) diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt index c24c3cfa..eea6c2aa 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt @@ -30,9 +30,6 @@ import java.io.File */ @SuppressLint("DiscouragedPrivateApi") class SettingsProvider : ContentProvider() { - companion object { - const val INTERVAL = 1 * 60 * 1000 // 1 minute - } private val preferences: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) @@ -167,9 +164,15 @@ class SettingsProvider : ContentProvider() { Gcm.NETWORK_ROAMING -> Integer.parseInt(preferences.getString(key, "0") ?: "0") Gcm.NETWORK_OTHER -> Integer.parseInt(preferences.getString(key, "0") ?: "0") +<<<<<<< HEAD Gcm.LEARNT_MOBILE -> preferences.getInt(key, INTERVAL) Gcm.LEARNT_WIFI -> preferences.getInt(key, INTERVAL) Gcm.LEARNT_OTHER -> preferences.getInt(key, INTERVAL) +======= + Gcm.LEARNT_MOBILE -> preferences.getInt(key, 300000) + Gcm.LEARNT_WIFI -> preferences.getInt(key, 300000) + Gcm.LEARNT_OTHER -> preferences.getInt(key, 300000) +>>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) else -> throw IllegalArgumentException("Unknown key: $key") } 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 7073b5fa..1d24c819 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 @@ -286,8 +286,8 @@ public class McsService extends Service implements Handler.Callback { public synchronized static long getCurrentDelay() { long delay = currentDelay == 0 ? 5000 : currentDelay; - if (currentDelay < SettingsProvider.INTERVAL) currentDelay += 10000; - if (currentDelay >= SettingsProvider.INTERVAL && currentDelay < 600000) currentDelay += SettingsProvider.INTERVAL; + if (currentDelay < 60000) currentDelay += 10000; + if (currentDelay >= 60000 && currentDelay < 600000) currentDelay += 60000; return delay; } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt index 77cf34b0..23e551be 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt @@ -11,7 +11,8 @@ import org.microg.gms.gcm.TriggerReceiver.FORCE_TRY_RECONNECT import org.microg.mgms.settings.SettingsContract import org.microg.mgms.settings.SettingsContract.Gcm import org.microg.mgms.settings.SettingsContract.setSettings -import org.microg.mgms.settings.SettingsProvider +import kotlin.math.max +import kotlin.math.min data class GcmPrefs( val isGcmLogEnabled: Boolean, @@ -37,6 +38,9 @@ data class GcmPrefs( const val PREF_NETWORK_ROAMING = Gcm.NETWORK_ROAMING const val PREF_NETWORK_OTHER = Gcm.NETWORK_OTHER + private const val MIN_INTERVAL = 5 * 60 * 1000 // 5 minutes + private const val MAX_INTERVAL = 15 * 60 * 1000 // 15 minutes + @JvmStatic fun get(context: Context): GcmPrefs? { return Gcm.getContentUri(context)?.let { @@ -93,6 +97,7 @@ data class GcmPrefs( } } + @Suppress("DEPRECATION") fun getNetworkPrefForInfo(info: NetworkInfo?): String { if (info == null) return PREF_NETWORK_OTHER return if (info.isRoaming) PREF_NETWORK_ROAMING else when (info.type) { @@ -102,25 +107,27 @@ data class GcmPrefs( } } + @Suppress("DEPRECATION") fun getHeartbeatMsFor(info: NetworkInfo?): Int { return getHeartbeatMsFor(getNetworkPrefForInfo(info)) } fun getHeartbeatMsFor(pref: String): Int { return if (PREF_NETWORK_ROAMING == pref) { - if (networkRoaming != 0) networkRoaming * SettingsProvider.INTERVAL else learntMobileInterval + if (networkRoaming != 0) networkRoaming * 60000 else learntMobileInterval } else if (PREF_NETWORK_MOBILE == pref) { - if (networkMobile != 0) networkMobile * SettingsProvider.INTERVAL else learntMobileInterval + if (networkMobile != 0) networkMobile * 60000 else learntMobileInterval } else if (PREF_NETWORK_WIFI == pref) { - if (networkWifi != 0) networkWifi * SettingsProvider.INTERVAL else learntWifiInterval + if (networkWifi != 0) networkWifi * 60000 else learntWifiInterval } else { - if (networkOther != 0) networkOther * SettingsProvider.INTERVAL else learntOtherInterval + if (networkOther != 0) networkOther * 60000 else learntOtherInterval } } fun learnTimeout(context: Context, pref: String) { Log.d("GmsGcmPrefs", "learnTimeout: $pref") when (pref) { +<<<<<<< HEAD PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> Gcm.getContentUri(context)?.let { setSettings(context, it) { val newInterval = (learntMobileInterval * 0.95).toInt() @@ -138,6 +145,19 @@ data class GcmPrefs( val newInterval = (learntMobileInterval * 0.95).toInt() put(Gcm.LEARNT_OTHER, newInterval) } +======= + PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> setSettings(context, Gcm.getContentUri(context)) { + val newInterval = (learntMobileInterval * 0.95).toInt() + put(Gcm.LEARNT_MOBILE, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) + } + PREF_NETWORK_WIFI -> setSettings(context, Gcm.getContentUri(context)) { + val newInterval = (learntWifiInterval * 0.95).toInt() + put(Gcm.LEARNT_WIFI, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) + } + else -> setSettings(context, Gcm.getContentUri(context)) { + val newInterval = (learntOtherInterval * 0.95).toInt() + put(Gcm.LEARNT_OTHER, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) +>>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) } } } @@ -147,34 +167,53 @@ data class GcmPrefs( when (pref) { PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> { if (time > learntMobileInterval / 4 * 3) { +<<<<<<< HEAD Gcm.getContentUri(context)?.let { setSettings(context, it) { put(Gcm.LEARNT_MOBILE, SettingsProvider.INTERVAL) } +======= + val newInterval = (learntMobileInterval * 1.02).toInt() + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LEARNT_MOBILE, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) +>>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) } } } PREF_NETWORK_WIFI -> { if (time > learntWifiInterval / 4 * 3) { +<<<<<<< HEAD Gcm.getContentUri(context)?.let { setSettings(context, it) { put(Gcm.LEARNT_WIFI, SettingsProvider.INTERVAL) } +======= + val newInterval = (learntWifiInterval * 1.02).toInt() + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LEARNT_WIFI, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) +>>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) } } } else -> { if (time > learntOtherInterval / 4 * 3) { +<<<<<<< HEAD Gcm.getContentUri(context)?.let { setSettings(context, it) { put(Gcm.LEARNT_OTHER, SettingsProvider.INTERVAL) } +======= + val newInterval = (learntOtherInterval * 1.02).toInt() + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LEARNT_OTHER, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) +>>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) } } } } } + @Suppress("DEPRECATION") fun isEnabledFor(info: NetworkInfo?): Boolean { return isEnabled && info != null && getHeartbeatMsFor(info) >= 0 } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt index d3cef96f..37bda799 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt @@ -37,7 +37,7 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(requireContext(), getGcmServiceInfo(appContext).configuration.copy(mobile = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(mobile = it)) } updateContent() } @@ -47,7 +47,7 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(appContext, getGcmServiceInfo(requireContext()).configuration.copy(wifi = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(wifi = it)) } updateContent() } @@ -57,7 +57,7 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(appContext, getGcmServiceInfo(requireContext()).configuration.copy(roaming = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(roaming = it)) } updateContent() } @@ -67,7 +67,7 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(appContext, getGcmServiceInfo(requireContext()).configuration.copy(other = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(other = it)) } updateContent() } @@ -98,13 +98,13 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { private fun getSummaryString(value: Int, learnt: Int): String = when (value) { -1 -> getString(R.string.service_status_disabled_short) 0 -> getString(R.string.service_status_enabled_short) + " / " + getString(R.string.gcm_status_pref_default) + ": " + getHeartbeatString(learnt) - else -> getString(R.string.service_status_enabled_short) + " / " + getString(R.string.gcm_status_pref_manual) + ": " + getHeartbeatString(value * SettingsProvider.INTERVAL) + else -> getString(R.string.service_status_enabled_short) + " / " + getString(R.string.gcm_status_pref_manual) + ": " + getHeartbeatString(value * 60000) } private fun getHeartbeatString(heartbeatMs: Int): String { return if (heartbeatMs < 120000) { (heartbeatMs / 1000).toString() + " " + getString(R.string.gcm_status_pref_sec) - } else (heartbeatMs / SettingsProvider.INTERVAL).toString() + " " + getString(R.string.gcm_status_pref_min) + } else (heartbeatMs / 60000).toString() + " " + getString(R.string.gcm_status_pref_min) } companion object { diff --git a/play-services-core/src/main/res/xml/preferences_gcm_advanced.xml b/play-services-core/src/main/res/xml/preferences_gcm_advanced.xml index 5c104f8a..efac059b 100755 --- a/play-services-core/src/main/res/xml/preferences_gcm_advanced.xml +++ b/play-services-core/src/main/res/xml/preferences_gcm_advanced.xml @@ -23,6 +23,7 @@ Date: Sun, 24 Dec 2023 02:21:27 +0900 Subject: [PATCH 12/63] Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b --- .../android/gms/common/data/DataHolder.java | 20 +++++++ .../gms/common/DeviceConfiguration.java | 16 ++++-- .../gms/common/ForegroundServiceContext.java | 20 ++++--- .../gms/common/MultiConnectionKeeper.java | 7 +-- .../security/ProviderInstallerImpl.java | 8 +-- .../gms/auth/AccountContentProvider.java | 15 ++++-- .../java/org/microg/gms/auth/AuthManager.java | 6 +-- .../microg/gms/checkin/TriggerReceiver.java | 3 +- .../java/org/microg/gms/gcm/GcmDatabase.java | 5 +- .../java/org/microg/gms/gcm/McsService.java | 53 +++++++++++++++---- .../org/microg/gms/gcm/TriggerReceiver.java | 3 +- .../gms/ui/AccountSettingsActivity.java | 4 +- .../org/microg/gms/ui/SettingsActivity.java | 5 +- 13 files changed, 118 insertions(+), 47 deletions(-) diff --git a/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java b/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java index 77acbb1b..abcdc6ca 100644 --- a/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java +++ b/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java @@ -100,6 +100,26 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return new Builder(columns); } +<<<<<<< HEAD +======= + /** + * @param statusCode The status code of this {@link DataHolder}. + * @param metadata The metadata associated with this {@link DataHolder} (may be null). + * @return An empty {@link DataHolder} object with the given status and metadata. + */ + public static DataHolder empty(int statusCode, Bundle metadata) { + return new DataHolder(new String[0], new CursorWindow[0], statusCode, metadata); + } + + /** + * @param statusCode The status code of this {@link DataHolder}. + * @return An empty {@link DataHolder} object with the given status and null metadata. + */ + public static DataHolder empty(int statusCode) { + return empty(statusCode, null); + } + +>>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) @SuppressLint({"NewApi", "ObsoleteSdkInt"}) static int getCursorType(Cursor cursor, int i) { return cursor.getType(i); diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java b/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java index d4ab5940..1246a50c 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java @@ -26,6 +26,8 @@ import android.opengl.GLES10; import android.os.Build; import android.util.DisplayMetrics; +import org.microg.gms.profile.ProfileManager; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -56,6 +58,7 @@ public class DeviceConfiguration { public int widthPixels; public DeviceConfiguration(Context context) { + ProfileManager.ensureInitialized(context); ConfigurationInfo configurationInfo = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getDeviceConfigurationInfo(); touchScreen = configurationInfo.reqTouchScreen; keyboardType = configurationInfo.reqKeyboardType; @@ -78,10 +81,10 @@ public class DeviceConfiguration { } Collections.sort(sharedLibraries); availableFeatures = new ArrayList<>(); - if (packageManager.getSystemAvailableFeatures() != null) { - for (FeatureInfo featureInfo : packageManager.getSystemAvailableFeatures()) { - if (featureInfo != null && featureInfo.name != null) availableFeatures.add(featureInfo.name); - } + packageManager.getSystemAvailableFeatures(); + for (FeatureInfo featureInfo : packageManager.getSystemAvailableFeatures()) { + if (featureInfo != null && featureInfo.name != null) + availableFeatures.add(featureInfo.name); } Collections.sort(availableFeatures); this.nativePlatforms = getNativePlatforms(); @@ -98,9 +101,12 @@ public class DeviceConfiguration { Collections.sort(this.glExtensions); } - @SuppressWarnings({"deprecation", "InlinedApi"}) + @SuppressWarnings({"InlinedApi"}) private static List getNativePlatforms() { +<<<<<<< HEAD List nativePlatforms; +======= +>>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) return Arrays.asList(Build.SUPPORTED_ABIS); } diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java b/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java index c7a2ff52..0cf5360a 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java @@ -1,5 +1,10 @@ package org.microg.gms.common; +<<<<<<< HEAD +======= +import static android.os.Build.VERSION.SDK_INT; + +>>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationChannel; @@ -11,7 +16,6 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.os.PowerManager; import android.provider.Settings; import android.util.Log; @@ -33,7 +37,7 @@ public class ForegroundServiceContext extends ContextWrapper { if (!isIgnoringBatteryOptimizations()) { Log.d(TAG, "Starting in foreground mode."); service.putExtra(EXTRA_FOREGROUND, true); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (SDK_INT >= 26) { return super.startForegroundService(service); } else { return super.startService(service); @@ -75,7 +79,7 @@ public class ForegroundServiceContext extends ContextWrapper { String serviceName = getServiceName(service); Log.d(tag, "Started " + serviceName + " in foreground mode."); try { - Notification notification = buildForegroundNotification(service, serviceName); + Notification notification = buildForegroundNotification(service); service.startForeground(serviceName.hashCode(), notification); Log.d(tag, "Notification: " + notification); } catch (Exception e) { @@ -85,9 +89,13 @@ public class ForegroundServiceContext extends ContextWrapper { } @SuppressLint("BatteryLife") +<<<<<<< HEAD private static Notification buildForegroundNotification(Context context, String serviceName) { +======= + private static Notification buildForegroundNotification(Context context) { +>>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) Intent notificationIntent = new Intent(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SDK_INT >= 31) { notificationIntent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); notificationIntent.setData(Uri.parse("package:" + context.getPackageName())); } else { @@ -97,8 +105,8 @@ public class ForegroundServiceContext extends ContextWrapper { PendingIntent notificationPendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, - 0); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + PendingIntent.FLAG_IMMUTABLE); + if (SDK_INT >= 26) { NotificationChannel Channel = new NotificationChannel("foreground-service", context.getResources().getString(R.string.notification_service_name), NotificationManager.IMPORTANCE_LOW); diff --git a/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java b/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java index 30c1d61c..616224bf 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java +++ b/play-services-base/src/main/java/org/microg/gms/common/MultiConnectionKeeper.java @@ -33,8 +33,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; public class MultiConnectionKeeper { @@ -164,10 +162,7 @@ public class MultiConnectionKeeper { } else { intent = gmsIntent; } - int flags = Context.BIND_AUTO_CREATE | Context.BIND_DEBUG_UNBIND; - if (SDK_INT >= ICE_CREAM_SANDWICH) { - flags |= Context.BIND_ADJUST_WITH_ACTIVITY; - } + int flags = Context.BIND_AUTO_CREATE | Context.BIND_DEBUG_UNBIND | Context.BIND_ADJUST_WITH_ACTIVITY; bound = context.bindService(intent, serviceConnection, flags); Log.d(TAG, "Connection(" + actionString + ") : bind() : bindService=" + bound); if (!bound) { diff --git a/play-services-core/src/main/java/com/google/android/gms/common/security/ProviderInstallerImpl.java b/play-services-core/src/main/java/com/google/android/gms/common/security/ProviderInstallerImpl.java index e25391e5..0270e626 100644 --- a/play-services-core/src/main/java/com/google/android/gms/common/security/ProviderInstallerImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/common/security/ProviderInstallerImpl.java @@ -5,11 +5,12 @@ package com.google.android.gms.common.security; +import static android.os.Build.VERSION.SDK_INT; import static com.google.android.gms.security.ProviderInstaller.PROVIDER_NAME; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.os.Build; import android.os.Process; import android.util.Log; @@ -53,13 +54,14 @@ public class ProviderInstallerImpl { String packageName = PackageUtils.packageFromProcessId(context, Process.myPid()); if (packageName != null && packageName.contains(".")) return packageName; try { + @SuppressLint("DiscouragedPrivateApi") Method getBasePackageName = Context.class.getDeclaredMethod("getBasePackageName"); packageName = (String) getBasePackageName.invoke(context); if (packageName != null) return packageName; - } catch (Exception e) { + } catch (Exception ignored) { } - if (Build.VERSION.SDK_INT >= 29) { + if (SDK_INT >= 29) { return context.getOpPackageName(); } Context applicationContext = context.getApplicationContext(); 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 9c96646b..1532a3ed 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 @@ -17,6 +17,7 @@ package org.microg.gms.auth; import static android.accounts.AccountManager.VISIBILITY_VISIBLE; +import static android.os.Build.VERSION.SDK_INT; import android.Manifest; import android.accounts.Account; @@ -27,7 +28,6 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -36,6 +36,7 @@ import androidx.annotation.Nullable; import org.microg.gms.common.PackageUtils; import java.util.Arrays; +import java.util.Objects; import static org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE; import static org.microg.gms.auth.AuthConstants.PROVIDER_EXTRA_ACCOUNTS; @@ -54,11 +55,15 @@ public class AccountContentProvider extends ContentProvider { @Nullable @Override public Bundle call(String method, String arg, Bundle extras) { +<<<<<<< HEAD String suggestedPackageName; suggestedPackageName = getCallingPackage(); +======= + String suggestedPackageName = getCallingPackage(); +>>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) String packageName = PackageUtils.getAndCheckCallingPackage(getContext(), suggestedPackageName); Log.d(TAG, "Call " + method + " from " + packageName + " with arg " + arg); - if (!PackageUtils.callerHasExtendedAccess(getContext())) { + if (!PackageUtils.callerHasExtendedAccess(Objects.requireNonNull(getContext()))) { String[] packagesForUid = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid()); if (packagesForUid != null && packagesForUid.length != 0) Log.w(TAG, "Not granting extended access to " + Arrays.toString(packagesForUid) @@ -72,10 +77,14 @@ public class AccountContentProvider extends ContentProvider { if (arg != null && (arg.equals(DEFAULT_ACCOUNT_TYPE) || arg.startsWith(DEFAULT_ACCOUNT_TYPE + "."))) { AccountManager am = AccountManager.get(getContext()); accounts = am.getAccountsByTypeForPackage(arg, packageName); +<<<<<<< HEAD if (accounts == null || accounts.length == 0) { +======= + if (accounts.length == 0) { +>>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) accounts = am.getAccountsByType(arg); } - if (Build.VERSION.SDK_INT >= 26 && accounts != null && arg.equals(DEFAULT_ACCOUNT_TYPE)) { + if (SDK_INT >= 26 && 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); diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java index a73450b7..1cf5df87 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java @@ -18,13 +18,13 @@ package org.microg.gms.auth; import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; +import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.auth.AuthPrefs.isTrustGooglePermitted; import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; import android.content.pm.PackageManager; -import android.os.Build; import android.util.Log; import org.microg.gms.base.core.BuildConfig; @@ -105,7 +105,7 @@ public class AuthManager { public void setPermitted(boolean value) { setUserData(buildPermKey(), value ? "1" : "0"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && value && packageName != null) { + if (SDK_INT >= 26 && value && packageName != null) { // Make account persistently visible as we already granted access accountManager.setAccountVisibility(getAccount(), packageName, AccountManager.VISIBILITY_VISIBLE); } @@ -170,7 +170,7 @@ public class AuthManager { public void setAuthToken(String service, String auth) { getAccountManager().setAuthToken(getAccount(), buildTokenKey(service), auth); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageName != null && auth != null) { + if (SDK_INT >= 26 && packageName != null && auth != null) { // Make account persistently visible as we already granted access accountManager.setAccountVisibility(getAccount(), packageName, AccountManager.VISIBILITY_VISIBLE); } diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java index 4b6551bd..752b8119 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java @@ -18,7 +18,6 @@ package org.microg.gms.checkin; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.checkin.CheckinService.EXTRA_FORCE_CHECKIN; import static org.microg.gms.checkin.CheckinService.REGULAR_CHECKIN_INTERVAL; @@ -54,7 +53,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { Intent subIntent = new Intent(context, CheckinService.class); subIntent.putExtra(EXTRA_FORCE_CHECKIN, force); startWakefulService(new ForegroundServiceContext(context), subIntent); - } else if (SDK_INT >= 23) { + } else { // no network, register a network callback to retry when we have internet NetworkRequest networkRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_INTERNET) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java b/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java index d9881dd8..d19ce6ea 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/GcmDatabase.java @@ -10,7 +10,6 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.os.Build; import android.text.TextUtils; import java.util.ArrayList; @@ -55,9 +54,7 @@ public class GcmDatabase extends SQLiteOpenHelper { public GcmDatabase(Context context) { super(context, DB_NAME, null, DB_VERSION); this.context = context; - if (Build.VERSION.SDK_INT >= 16) { - this.setWriteAheadLoggingEnabled(true); - } + this.setWriteAheadLoggingEnabled(true); } public static class App { 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 1d24c819..c8fd30b0 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 @@ -19,8 +19,42 @@ package org.microg.gms.gcm; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.common.PackageUtils.warnIfNotPersistentProcess; -import static org.microg.gms.gcm.GcmConstants.*; -import static org.microg.gms.gcm.McsConstants.*; +import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE; +import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; +import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_OVERRIDE; +import static org.microg.gms.gcm.GcmConstants.EXTRA_COLLAPSE_KEY; +import static org.microg.gms.gcm.GcmConstants.EXTRA_FROM; +import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_ID; +import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSENGER; +import static org.microg.gms.gcm.GcmConstants.EXTRA_RAWDATA; +import static org.microg.gms.gcm.GcmConstants.EXTRA_RAWDATA_BASE64; +import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID; +import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_FROM; +import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_TO; +import static org.microg.gms.gcm.GcmConstants.EXTRA_TTL; +import static org.microg.gms.gcm.McsConstants.ACTION_ACK; +import static org.microg.gms.gcm.McsConstants.ACTION_CONNECT; +import static org.microg.gms.gcm.McsConstants.ACTION_HEARTBEAT; +import static org.microg.gms.gcm.McsConstants.ACTION_RECONNECT; +import static org.microg.gms.gcm.McsConstants.ACTION_SEND; +import static org.microg.gms.gcm.McsConstants.EXTRA_REASON; +import static org.microg.gms.gcm.McsConstants.MCS_CLOSE_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_DATA_MESSAGE_STANZA_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_HEARTBEAT_ACK_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_HEARTBEAT_PING_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_IQ_STANZA_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_LOGIN_REQUEST_TAG; +import static org.microg.gms.gcm.McsConstants.MCS_LOGIN_RESPONSE_TAG; +import static org.microg.gms.gcm.McsConstants.MSG_ACK; +import static org.microg.gms.gcm.McsConstants.MSG_CONNECT; +import static org.microg.gms.gcm.McsConstants.MSG_HEARTBEAT; +import static org.microg.gms.gcm.McsConstants.MSG_INPUT; +import static org.microg.gms.gcm.McsConstants.MSG_INPUT_ERROR; +import static org.microg.gms.gcm.McsConstants.MSG_OUTPUT; +import static org.microg.gms.gcm.McsConstants.MSG_OUTPUT_DONE; +import static org.microg.gms.gcm.McsConstants.MSG_OUTPUT_ERROR; +import static org.microg.gms.gcm.McsConstants.MSG_OUTPUT_READY; +import static org.microg.gms.gcm.McsConstants.MSG_TEARDOWN; import android.annotation.SuppressLint; import android.app.AlarmManager; @@ -34,7 +68,6 @@ import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -67,7 +100,6 @@ import org.microg.gms.gcm.mcs.IqStanza; import org.microg.gms.gcm.mcs.LoginRequest; import org.microg.gms.gcm.mcs.LoginResponse; import org.microg.gms.gcm.mcs.Setting; -import org.microg.mgms.settings.SettingsProvider; import java.io.Closeable; import java.lang.reflect.Field; @@ -138,10 +170,10 @@ public class McsService extends Service implements Handler.Callback { @Nullable private Method addPowerSaveTempWhitelistAppMethod; @Nullable - @RequiresApi(Build.VERSION_CODES.S) + @RequiresApi(31) private Object powerExemptionManager; @Nullable - @RequiresApi(Build.VERSION_CODES.S) + @RequiresApi(31) private Method addToTemporaryAllowListMethod; private class HandlerThread extends Thread { @@ -156,7 +188,7 @@ public class McsService extends Service implements Handler.Callback { wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "mcs"); wakeLock.setReferenceCounted(false); synchronized (McsService.class) { - rootHandler = new Handler(Looper.myLooper(), McsService.this); + rootHandler = new Handler(Objects.requireNonNull(Looper.myLooper()), McsService.this); if (connectIntent != null) { rootHandler.sendMessage(rootHandler.obtainMessage(MSG_CONNECT, connectIntent)); WakefulBroadcastReceiver.completeWakefulIntent(connectIntent); @@ -181,7 +213,7 @@ public class McsService extends Service implements Handler.Callback { powerManager = (PowerManager) getSystemService(POWER_SERVICE); if (checkSelfPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") == PackageManager.PERMISSION_GRANTED) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SDK_INT >= 31) { Class powerExemptionManagerClass = Class.forName("android.os.PowerExemptionManager"); powerExemptionManager = getSystemService(powerExemptionManagerClass); addToTemporaryAllowListMethod = @@ -280,7 +312,10 @@ public class McsService extends Service implements Handler.Callback { closeAll(); } logd(context, "Scheduling heartbeat in " + heartbeatMs / 1000 + " seconds..."); +<<<<<<< HEAD // This is supposed to work even when running in idle and without battery optimization disabled +======= +>>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); } @@ -593,7 +628,7 @@ public class McsService extends Service implements Handler.Callback { } private void addPowerSaveTempWhitelistApp(String packageName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SDK_INT >= 31) { try { if (addToTemporaryAllowListMethod != null && powerExemptionManager != null) { logd(this, "Adding app " + packageName + " to the temp allowlist"); diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java index 245d5c2a..e94b2f02 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java @@ -17,7 +17,6 @@ package org.microg.gms.gcm; import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.N; import static org.microg.gms.gcm.McsConstants.ACTION_CONNECT; import static org.microg.gms.gcm.McsConstants.ACTION_HEARTBEAT; import static org.microg.gms.gcm.McsConstants.EXTRA_REASON; @@ -44,7 +43,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { * "Project Svelte" is just there to f**k things up... */ public synchronized static void register(Context context) { - if (SDK_INT >= N && !registered) { + if (SDK_INT >= 24 && !registered) { IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"); context.getApplicationContext().registerReceiver(new TriggerReceiver(), intentFilter); registered = true; 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 fa099d49..94dd68a9 100755 --- 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 @@ -18,7 +18,6 @@ package org.microg.gms.ui; import android.accounts.Account; import android.accounts.AccountManager; -import android.os.Build; import android.os.Bundle; import androidx.annotation.Nullable; @@ -34,6 +33,7 @@ import org.microg.tools.ui.ResourceSettingsFragment; import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE; import static android.accounts.AccountManager.VISIBILITY_NOT_VISIBLE; import static android.accounts.AccountManager.VISIBILITY_VISIBLE; +import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.auth.AuthManager.PREF_AUTH_VISIBLE; public class AccountSettingsActivity extends AbstractSettingsActivity { @@ -53,7 +53,7 @@ public class AccountSettingsActivity extends AbstractSettingsActivity { super.onCreatePreferences(savedInstanceState, rootKey); Preference pref = findPreference(PREF_AUTH_VISIBLE); if (pref != null) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + if (SDK_INT < 26) { pref.setVisible(false); } else { pref.setOnPreferenceChangeListener((preference, newValue) -> { diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java index 32230055..d706f213 100755 --- a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java @@ -1,6 +1,7 @@ package org.microg.gms.ui; -import android.os.Build; +import static android.os.Build.VERSION.SDK_INT; + import android.os.Bundle; import androidx.annotation.Nullable; @@ -26,7 +27,7 @@ public class SettingsActivity extends AppCompatActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.settings_root_activity); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SDK_INT >= 31) { DynamicColors.applyToActivityIfAvailable(this); } appBarConfiguration = new AppBarConfiguration.Builder(getNavController().getGraph()).build(); From 287158a26455dfe36fc2e98cc5c994312658df57 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 24 Dec 2023 02:29:39 +0900 Subject: [PATCH 13/63] Allow to compress the libraries https://github.com/microg/GmsCore/commit/4651ed55308f2113b0ada49884dd320165dc5e10 --- play-services-core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 1e97f468..f44f7fd7 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -107,7 +107,7 @@ android { packagingOptions { jniLibs { - useLegacyPackaging false + useLegacyPackaging true } resources { excludes += ['META-INF/ASL2.0'] From 0b8b6208b2829ac9fb0b0b94a043c2fe2b252b8a Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 24 Dec 2023 04:29:21 +0900 Subject: [PATCH 14/63] Add Emulator profiles https://github.com/microg/GmsCore/commit/0338543570246870f7bb4b982ab3f12505f9b666 --- .../src/main/res/xml/profile_bullhead_27.xml | 36 ++++++++++++++++++ .../res/xml/profile_lineage_falcon_25.xml | 36 ++++++++++++++++++ .../src/main/res/xml/profile_redfin_33.xml | 37 +++++++++++++++++++ .../res/xml/profile_sdk_gphone64_arm64_33.xml | 37 +++++++++++++++++++ .../xml/profile_sdk_gphone64_x86_64_33.xml | 37 +++++++++++++++++++ 5 files changed, 183 insertions(+) create mode 100644 play-services-core/src/main/res/xml/profile_bullhead_27.xml create mode 100644 play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml create mode 100644 play-services-core/src/main/res/xml/profile_redfin_33.xml create mode 100644 play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml create mode 100644 play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml diff --git a/play-services-core/src/main/res/xml/profile_bullhead_27.xml b/play-services-core/src/main/res/xml/profile_bullhead_27.xml new file mode 100644 index 00000000..186a4356 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_bullhead_27.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml b/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml new file mode 100644 index 00000000..57beb5d4 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/res/xml/profile_redfin_33.xml b/play-services-core/src/main/res/xml/profile_redfin_33.xml new file mode 100644 index 00000000..52c57f88 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_redfin_33.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml b/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml new file mode 100644 index 00000000..62d223c5 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml b/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml new file mode 100644 index 00000000..b616c365 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bb405b5b5845b18788c66ffa4b9ed419e82f983d Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 24 Dec 2023 04:55:14 +0900 Subject: [PATCH 15/63] Add basic Google account settings https://github.com/microg/GmsCore/commit/dcf5f1fecb9add7e640a6bb780cb0c752e5c025e --- .../src/main/AndroidManifest.xml | 36 +++- ...ava => LegacyAccountSettingsActivity.java} | 2 +- .../microg/tools/AccountPickerActivity.java | 19 +- .../gms/accountsettings/ui/LoaderActivity.kt | 165 ++++++++++++++++ .../gms/accountsettings/ui/MainActivity.kt | 185 ++++++++++++++++++ .../gms/accountsettings/ui/WebViewHelper.kt | 130 ++++++++++++ .../gms/accountsettings/ui/extensions.kt | 23 +++ .../src/main/res/xml/preferences_auth.xml | 7 + 8 files changed, 549 insertions(+), 18 deletions(-) rename play-services-core/src/main/java/org/microg/gms/ui/{AccountSettingsActivity.java => LegacyAccountSettingsActivity.java} (97%) mode change 100755 => 100644 create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/LoaderActivity.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/WebViewHelper.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index a38f4479..d4d1998d 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -256,7 +256,8 @@ android:name="org.microg.tools.AccountPickerActivity" android:excludeFromRecents="true" android:exported="true" - android:process=":ui"> + android:process=":ui" + android:theme="@style/Theme.LoginBlue.Base"> @@ -385,20 +386,49 @@ android:taskAffinity="org.microg.gms.settings" /> +======= + android:taskAffinity="org.microg.gms.settings" /> + + +>>>>>>> 902d5e3d (Add basic Google account settings https://github.com/microg/GmsCore/commit/dcf5f1fecb9add7e640a6bb780cb0c752e5c025e) + - + + + + + + + + + + + + + 1) { + if (canAskForAccount) { + val intent = Intent(this, AccountPickerActivity::class.java) + intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES, arrayOf(DEFAULT_ACCOUNT_TYPE)) + startActivityForResult(intent, REQUEST_ACCOUNT_PICKER) + canAskForAccount = false + return + } else { + return finishResult(RESULT_CANCELED) + } + } else { + accounts.first() + } + + val intent = Intent(this, MainActivity::class.java).apply { + action = intent.action + if (intent.hasExtra(EXTRA_THEME_CHOICE)) putExtra(EXTRA_THEME_CHOICE, intent.getIntExtra(EXTRA_THEME_CHOICE, 0)) + putExtra(EXTRA_ACCOUNT_NAME, account.name) + if (ignoreAccount) putExtra(EXTRA_IGNORE_ACCOUNT, true) + putExtra(EXTRA_SCREEN_ID, intent.getIntExtra(EXTRA_SCREEN_ID, 1)) + for (it in intent.extras?.keySet().orEmpty()) { + if (it.startsWith(EXTRA_SCREEN_OPTIONS_PREFIX)) putExtra(it, intent.getStringExtra(it)) + } + putExtra(EXTRA_CALLING_PACKAGE_NAME, callingActivity?.packageName) + if (intent.action != ACTION_BROWSE_SETTINGS) { + addFlags(FLAG_ACTIVITY_FORWARD_RESULT) + } + } + + startActivity(intent) + + if (!isFinishing && !isChangingConfigurations) { + finishResult(RESULT_OK) + } + } + + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == REQUEST_ACCOUNT_PICKER) { + if (resultCode == RESULT_OK && data?.hasExtra(AccountManager.KEY_ACCOUNT_NAME) == true) { + intent.putExtra(EXTRA_ACCOUNT_NAME, data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)) + launchMain() + } else { + finishResult(resultCode) + } + } else { + super.onActivityResult(requestCode, resultCode, data) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + canAskForAccount = true + val extras = intent?.extras?.also { it.keySet() } + Log.d(TAG, "Invoked with ${intent.action} and extras $extras") + + super.onCreate(savedInstanceState) + + val isMainAllowed = if (intent == null || intent.action != ACTION_BROWSE_SETTINGS) + PackageUtils.isGooglePackage(this, callingActivity?.packageName) + else + extras?.getInt(EXTRA_SCREEN_ID, -1) in BROWSABLE_SCREEN_IDS + + if (!isMainAllowed) { + launchFallback() + } else if (!isFinishing && !isChangingConfigurations){ + launchMain() + } + } + + private fun finishResult(resultCode: Int) { + setResult(resultCode) + finish() + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt new file mode 100644 index 00000000..441a2e3b --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.accountsettings.ui + +import android.accounts.Account +import android.accounts.AccountManager +import android.os.Bundle +import android.util.Log +import android.view.View +import android.webkit.WebView +import android.widget.ProgressBar +import android.widget.RelativeLayout +import android.widget.RelativeLayout.LayoutParams.MATCH_PARENT +import android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT +import androidx.appcompat.app.AppCompatActivity +import org.microg.gms.auth.AuthConstants +import org.microg.gms.common.Constants + +private const val TAG = "AccountSettings" + +// TODO: There likely is some API to figure those out... +private val SCREEN_ID_TO_URL = hashMapOf( + 1 to "https://myaccount.google.com", + 200 to "https://myaccount.google.com/privacycheckup", + 203 to "https://myaccount.google.com/email", + 204 to "https://myaccount.google.com/phone", + 205 to "https://myaccount.google.com/birthday", + 206 to "https://myaccount.google.com/gender", + 210 to "https://myaccount.google.com/locationsharing", + 214 to "https://myaccount.google.com/dashboard", + 215 to "https://takeout.google.com", + 216 to "https://myaccount.google.com/inactive", + 219 to "https://myactivity.google.com/myactivity", + 220 to "https://www.google.com/maps/timeline", + 224 to "https://myactivity.google.com/activitycontrols?settings=search", + 227 to "https://myactivity.google.com/activitycontrols?settings=location", + 231 to "https://myactivity.google.com/activitycontrols?settings=youtube", + 235 to "https://myactivity.google.com/activitycontrols/youtube", + 238 to "https://www.google.com/setting/search/privateresults/", + 241 to "https://myaccount.google.com/communication-preferences", + 300 to "https://myaccount.google.com/language", + 301 to "https://drive.google.com/settings/storage", + 302 to "https://myaccount.google.com/deleteservices", + 303 to "https://myaccount.google.com/deleteaccount", + 307 to "https://payments.google.com/payments/home", + 308 to "https://myaccount.google.com/subscriptions", + 309 to "https://myaccount.google.com/purchases", + 310 to "https://myaccount.google.com/reservations", + 312 to "https://myaccount.google.com/accessibility", + 313 to "https://myaccount.google.com/inputtools", + 400 to "https://myaccount.google.com/security-checkup/", + 401 to "https://myaccount.google.com/signinoptions/password", + 403 to "https://myaccount.google.com/signinoptions/two-step-verification", + 406 to "https://myaccount.google.com/signinoptions/rescuephone", + 407 to "https://myaccount.google.com/recovery/email", + 409 to "https://myaccount.google.com/notifications", + 410 to "https://myaccount.google.com/device-activity", + 417 to "https://myaccount.google.com/find-your-phone", + 425 to "https://myaccount.google.com/account-enhanced-safe-browsing", + 426 to "https://myaccount.google.com/two-step-verification/authenticator", + 427 to "https://myaccount.google.com/two-step-verification/backup-codes", + 429 to "https://myaccount.google.com/two-step-verification/security-keys", + 430 to "https://myaccount.google.com/two-step-verification/prompt", + 431 to "https://myaccount.google.com/connections", + 432 to "https://myaccount.google.com/two-step-verification/phone-numbers", + 433 to "https://myaccount.google.com/signinoptions/passkeys", + 437 to "https://myaccount.google.com/signinoptions/passwordoptional", + 500 to "https://policies.google.com/privacy", + 503 to "https://policies.google.com/terms", + 519 to "https://myaccount.google.com/yourdata/maps", + 520 to "https://myaccount.google.com/yourdata/search", + 530 to "https://fit.google.com/privacy/settings", + 547 to "https://myactivity.google.com/product/search", + 562 to "https://myaccount.google.com/yourdata/youtube", + 10003 to "https://myaccount.google.com/personal-info", + 10004 to "https://myaccount.google.com/data-and-privacy", + 10005 to "https://myaccount.google.com/people-and-sharing", + 10006 to "https://myaccount.google.com/security", + 10007 to "https://myaccount.google.com/payments-and-subscriptions", + 10015 to "https://support.google.com/accounts", + 10050 to "https://myaccount.google.com/profile", + 10090 to "https://myaccount.google.com/profile/name", + 10704 to "https://www.google.com/account/about", + 10706 to "https://myaccount.google.com/profile/profiles-summary", + 10728 to "https://myaccount.google.com/data-and-privacy/how-data-improves-experience", + 10729 to "https://myaccount.google.com/data-and-privacy/data-visibility", + 10759 to "https://myaccount.google.com/address/home", + 10760 to "https://myaccount.google.com/address/work", +) + +private val ALLOWED_WEB_PREFIXES = setOf( + "https://accounts.google.com/", + "https://myaccount.google.com/", + "https://one.google.com/", + "https://myactivity.google.com/", + "https://timeline.google.com/", + "https://takeout.google.com/", + "https://www.google.com/maps/timeline", + "https://www.google.com/setting/", + "https://drive.google.com/settings/", + "https://drive.google.com/accounts/", + "https://drive.google.com/u/1/settings/", + "https://payments.google.com/", + "https://policies.google.com/", + "https://fit.google.com/privacy/settings", + "https://maps.google.com/maps/timeline" +) + +private val ACTION_TO_SCREEN_ID = hashMapOf( + ACTION_MY_ACCOUNT to 1, + ACTION_ACCOUNT_PREFERENCES_SETTINGS to 1, + ACTION_SECURITY_SETTINGS to 10006, + ACTION_PRIVACY_SETTINGS to 10004, +) + +@Suppress("DEPRECATION") +class MainActivity : AppCompatActivity() { + private lateinit var webView: WebView + + private fun getSelectedAccountName(): String? = null + + override fun onCreate(savedInstanceState: Bundle?) { + val extras = intent?.extras?.also { it.keySet() } + Log.d(TAG, "Invoked with ${intent.action} and extras $extras") + super.onCreate(savedInstanceState) + + val screenId = intent?.getIntExtra(EXTRA_SCREEN_ID, -1).takeIf { it != -1 } ?: ACTION_TO_SCREEN_ID[intent.action] ?: 1 + val screenOptions = intent.extras?.keySet().orEmpty() + .filter { it.startsWith(EXTRA_SCREEN_OPTIONS_PREFIX) } + .map { it.substring(EXTRA_SCREEN_OPTIONS_PREFIX.length) to intent.getStringExtra(it) } + .toMap() + + val callingPackage = intent?.getStringExtra(EXTRA_CALLING_PACKAGE_NAME) ?: callingActivity?.packageName ?: Constants.GMS_PACKAGE_NAME + + val ignoreAccount = intent?.getBooleanExtra(EXTRA_IGNORE_ACCOUNT, false) ?: false + val accountName = if (ignoreAccount) null else { + val accounts = AccountManager.get(this).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) + val accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME) ?: intent.getParcelableExtra("account")?.name ?: getSelectedAccountName() + accounts.find { it.name.equals(accountName) }?.name + } + + if (accountName == null) { + Log.w(TAG, "No account, going without!") + } + + if (screenId in SCREEN_ID_TO_URL) { + val layout = RelativeLayout(this) + layout.addView(ProgressBar(this).apply { + layoutParams = RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + addRule(RelativeLayout.CENTER_HORIZONTAL) + addRule(RelativeLayout.CENTER_VERTICAL) + } + isIndeterminate = true + }) + webView = WebView(this).apply { + layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + visibility = View.INVISIBLE + } + layout.addView(webView) + setContentView(layout) + WebViewHelper(this, webView, ALLOWED_WEB_PREFIXES).openWebView(SCREEN_ID_TO_URL[screenId], accountName) + setResult(RESULT_OK) + } else { + Log.w(TAG, "Unknown screen id, can't open corresponding web page") + setResult(RESULT_CANCELED) + finish() + } + } + + override fun onDestroy() { + super.onDestroy() + } + + @Deprecated("Deprecated in Java") + override fun onBackPressed() { + if (this::webView.isInitialized && webView.canGoBack()) { + webView.goBack() + } else { + super.onBackPressed() + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/WebViewHelper.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/WebViewHelper.kt new file mode 100644 index 00000000..7ce98e9c --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/WebViewHelper.kt @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.accountsettings.ui + +import android.content.Intent +import android.content.Intent.URI_INTENT_SCHEME +import android.net.Uri +import android.util.Log +import android.view.View +import android.webkit.WebResourceRequest +import android.webkit.WebSettings +import android.webkit.WebView +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import androidx.webkit.WebResourceErrorCompat +import androidx.webkit.WebViewClientCompat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.microg.gms.auth.AuthManager +import org.microg.gms.common.Constants.GMS_PACKAGE_NAME +import org.microg.gms.common.PackageUtils +import java.net.URLEncoder +import java.util.* + +private const val TAG = "AccountSettingsWebView" + +class WebViewHelper(private val activity: AppCompatActivity, private val webView: WebView, private val allowedPrefixes: Set = emptySet()) { + fun openWebView(url: String?, accountName: String?) { + prepareWebViewSettings(webView.settings) + webView.webViewClient = object : WebViewClientCompat() { + override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceErrorCompat) { + Log.w(TAG, "Error loading: $error") + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + webView.visibility = View.VISIBLE + } + + @Deprecated("Deprecated in Java") + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + Log.d(TAG, "Navigating to $url") + if (url.startsWith("intent:")) { + try { + val intent = Intent.parseUri(url, URI_INTENT_SCHEME) + if (intent.`package` == GMS_PACKAGE_NAME || PackageUtils.isGooglePackage(activity, intent.`package`)) { + // Only allow to start Google packages + activity.startActivity(intent) + } else { + Log.w(TAG, "Ignoring request to start non-Google app") + } + } catch (e: Exception) { + Log.w(TAG, "Error invoking intent", e) + } + return false + } + if (allowedPrefixes.isNotEmpty() && allowedPrefixes.none { url.startsWith(it) }) { + try { + activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { addCategory(Intent.CATEGORY_BROWSABLE) }) + } catch (e: Exception) { + Log.w(TAG, "Error forwarding to browser", e) + } + return true + } + return false + } + } + + val urlWithLanguage: String? = addLanguageParam(url) + if (accountName != null) { + activity.lifecycleScope.launch { + withContext(Dispatchers.IO) { + openWebWithAccount(accountName, urlWithLanguage) + } + } + } else { + loadWebViewUrl(urlWithLanguage) + } + } + + private fun loadWebViewUrl(url: String?) { + if (url != null) { + webView.loadUrl(url) + } else { + activity.finish() + } + } + + private fun addLanguageParam(url: String?): String? { + val language = Locale.getDefault().language + return if (language.isNotEmpty()) { + Uri.parse(url).buildUpon().appendQueryParameter("hl", language).toString() + } else { + url + } + } + + private fun openWebWithAccount(accountName: String, url: String?) { + try { + val service = "weblogin:continue=" + URLEncoder.encode(url, "utf-8") + val authManager = AuthManager(activity, accountName, GMS_PACKAGE_NAME, service) + val authUrl = authManager.requestAuth(false)?.auth + if (authUrl?.contains("WILL_NOT_SIGN_IN") == true) { + throw RuntimeException("Would not sign in") + } + Log.d(TAG, "Opening $authUrl") + webView.post { + loadWebViewUrl(authUrl) + } + } catch (e: Exception) { + Log.w(TAG, "Failed to get weblogin auth.", e) + activity.finish() + } + } + + private fun prepareWebViewSettings(settings: WebSettings) { + settings.javaScriptEnabled = true + settings.setSupportMultipleWindows(false) + settings.allowFileAccess = false + settings.databaseEnabled = false + settings.setNeedInitialFocus(false) + settings.useWideViewPort = false + settings.setSupportZoom(false) + settings.javaScriptCanOpenWindowsAutomatically = false + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt new file mode 100644 index 00000000..5ca88fe7 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.accountsettings.ui + +const val ACTION_BROWSE_SETTINGS = "com.google.android.gms.accountsettings.action.BROWSE_SETTINGS" +const val ACTION_MY_ACCOUNT = "com.google.android.gms.accountsettings.MY_ACCOUNT" +const val ACTION_ACCOUNT_PREFERENCES_SETTINGS = "com.google.android.gms.accountsettings.ACCOUNT_PREFERENCES_SETTINGS" +const val ACTION_PRIVACY_SETTINGS = "com.google.android.gms.accountsettings.PRIVACY_SETTINGS" +const val ACTION_SECURITY_SETTINGS = "com.google.android.gms.accountsettings.SECURITY_SETTINGS" + +const val EXTRA_CALLING_PACKAGE_NAME = "extra.callingPackageName" +const val EXTRA_IGNORE_ACCOUNT = "extra.ignoreAccount" +const val EXTRA_ACCOUNT_NAME = "extra.accountName" +const val EXTRA_SCREEN_ID = "extra.screenId" +const val EXTRA_SCREEN_OPTIONS_PREFIX = "extra.screen." +const val EXTRA_FALLBACK_URL = "extra.fallbackUrl" +const val EXTRA_FALLBACK_AUTH = "extra.fallbackAuth" +const val EXTRA_THEME_CHOICE = "extra.themeChoice" + +const val OPTION_SCREEN_FLAVOR = "screenFlavor" \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/preferences_auth.xml b/play-services-core/src/main/res/xml/preferences_auth.xml index 9bebc16d..67cb2693 100755 --- a/play-services-core/src/main/res/xml/preferences_auth.xml +++ b/play-services-core/src/main/res/xml/preferences_auth.xml @@ -36,4 +36,11 @@ android:action="@string/var_pref_auth_action_preferences_settings" android:targetPackage="@string/var_pref_start_gms_package" /> + + + From f4bebc4489a22198180342761cb2fd4d91ab5ecd Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Mon, 25 Dec 2023 21:00:42 +0900 Subject: [PATCH 16/63] Keep Conscrypt classes in the primary dex file https://github.com/microg/GmsCore/commit/640be73476473ce412da4058f4b2a2aaa8c20a25 --- play-services-core/build.gradle | 3 +++ play-services-core/multidex-keep.pro | 9 +++++++++ play-services-core/proguard-rules.pro | 3 +++ proguard.flags | 15 ++++++++++++++- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 play-services-core/multidex-keep.pro diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index f44f7fd7..60485c49 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -62,6 +62,9 @@ android { buildConfigField "String", "BASE_PACKAGE_NAME", "\"$basePackageName\"" + multiDexEnabled true + multiDexKeepProguard file('multidex-keep.pro') + ndk { abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" } diff --git a/play-services-core/multidex-keep.pro b/play-services-core/multidex-keep.pro new file mode 100644 index 00000000..0cd8699e --- /dev/null +++ b/play-services-core/multidex-keep.pro @@ -0,0 +1,9 @@ +# Make sure maps is in the primary dex file +-keep class com.google.android.gms.maps.** { *; } +-keep class org.microg.gms.maps.** { *; } +-keep class com.mapbox.** { *; } +-keep class org.oscim.** { *; } + +# Keep Conscrypt in the primary dex file otherwise it will error out on legacy Android versions +-keep class com.google.android.gms.common.security.ProviderInstallerImpl { *; } +-keep class com.google.android.gms.org.conscrypt.** { *; } diff --git a/play-services-core/proguard-rules.pro b/play-services-core/proguard-rules.pro index 9b421ddd..923a7661 100644 --- a/play-services-core/proguard-rules.pro +++ b/play-services-core/proguard-rules.pro @@ -21,10 +21,13 @@ # Keep dynamically loaded GMS classes -keep public class com.google.android.gms.common.security.ProviderInstallerImpl { public *; } +-keep public class com.google.android.gms.dynamic.IObjectWrapper { public *; } +-keep public class com.google.android.gms.dynamite.descriptors.** { public *; } # Keep AutoSafeParcelables -keep public class * extends org.microg.safeparcel.AutoSafeParcelable { @org.microg.safeparcel.SafeParceled *; + (...); } # Keep form data diff --git a/proguard.flags b/proguard.flags index fba8c5e2..7e287ce2 100644 --- a/proguard.flags +++ b/proguard.flags @@ -11,16 +11,24 @@ -dontwarn com.caverock.androidsvg.** -dontwarn org.slf4j.** -dontwarn org.codehaus.jackson.** +-dontwarn com.android.location.provider.** +-dontwarn java.lang.instrument.ClassFileTransformer +-dontwarn sun.misc.SignalHandler +-dontwarn com.android.org.conscrypt.SSLParametersImpl +-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl # Disable ProGuard Notes, they won't help here -dontnote # Keep dynamically loaded GMS classes -keep public class com.google.android.gms.common.security.ProviderInstallerImpl { public *; } +-keep public class com.google.android.gms.dynamic.IObjectWrapper { public *; } +-keep public class com.google.android.gms.dynamite.descriptors.** { public *; } # Keep AutoSafeParcelables -keep public class * extends org.microg.safeparcel.AutoSafeParcelable { @org.microg.safeparcel.SafeParceled *; + (...); } # Keep form data @@ -29,7 +37,12 @@ } # Keep our stuff --keep class org.microg.** { *; } +-keep class org.microg** { *; } +-keep class com.google.android.gms** { *; } +-keep class com.mgoogle** { *; } +-keepdirectories org/microg** +-keepdirectories com/google/android/gms** +-keepdirectories com/mgoogle** # Keep asInterface method cause it's accessed from SafeParcel -keepattributes InnerClasses From 3ae0869aed0dc671938c21e8f359bf4e2bee288a Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Mon, 25 Dec 2023 21:02:30 +0900 Subject: [PATCH 17/63] Fix account setting crash under Android < 5 https://github.com/microg/GmsCore/commit/11001291e19333a1cb37f77196067222c76b4fb7 --- play-services-core/src/main/res/xml/preferences_auth.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/play-services-core/src/main/res/xml/preferences_auth.xml b/play-services-core/src/main/res/xml/preferences_auth.xml index 67cb2693..18007889 100755 --- a/play-services-core/src/main/res/xml/preferences_auth.xml +++ b/play-services-core/src/main/res/xml/preferences_auth.xml @@ -40,6 +40,7 @@ android:key="legacy" android:title="Legacy settings"> From 51103b583312de9b9c0bc6862a3641513a1ae7d4 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Mon, 25 Dec 2023 21:06:51 +0900 Subject: [PATCH 18/63] Push: Don't try adding app extra to network request https://github.com/microg/GmsCore/commit/23d1b18e28f04d0a2316693abac63ca13df5033b --- .../src/main/java/org/microg/gms/gcm/RegisterRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java index dbc21088..80c7fd83 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java @@ -123,7 +123,7 @@ public class RegisterRequest extends HttpFormClient.Request { public RegisterRequest extraParams(Bundle extraBundle) { for (String key : extraBundle.keySet()) { - if (!key.equals(GcmConstants.EXTRA_SENDER) && !key.equals(GcmConstants.EXTRA_DELETE)) { + if (!key.equals(GcmConstants.EXTRA_SENDER) && !key.equals(GcmConstants.EXTRA_DELETE) && !key.equals(GcmConstants.EXTRA_APP)) { extraParam(key, extraBundle.getString(key)); } } From 2f0175f825c07ec06d7f8ff0abcb30661e900b4c Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Mon, 25 Dec 2023 21:21:04 +0900 Subject: [PATCH 19/63] Use fake-signature module https://github.com/microg/GmsCore/commit/0f04df65e0deca5ca58b6d377a464d27f669f06e --- fake-signature/build.gradle | 26 + fake-signature/src/main/AndroidManifest.xml | 1159 +++++++++++++++++ fake-signature/src/main/res/values/arrays.xml | 15 + .../src/main/res/values/signature.xml | 23 +- play-services-core/build.gradle | 2 + .../src/main/AndroidManifest.xml | 4 - settings.gradle | 2 + 7 files changed, 1212 insertions(+), 19 deletions(-) create mode 100644 fake-signature/build.gradle create mode 100644 fake-signature/src/main/AndroidManifest.xml create mode 100644 fake-signature/src/main/res/values/arrays.xml rename {play-services-core => fake-signature}/src/main/res/values/signature.xml (56%) mode change 100755 => 100644 diff --git a/fake-signature/build.gradle b/fake-signature/build.gradle new file mode 100644 index 00000000..19f0fc40 --- /dev/null +++ b/fake-signature/build.gradle @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' + +android { + namespace "org.microg.signature.fake" + + compileSdkVersion androidCompileSdk + buildToolsVersion androidBuildVersionTools + + defaultConfig { + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { +} diff --git a/fake-signature/src/main/AndroidManifest.xml b/fake-signature/src/main/AndroidManifest.xml new file mode 100644 index 00000000..1b3ae15d --- /dev/null +++ b/fake-signature/src/main/AndroidManifest.xml @@ -0,0 +1,1159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fake-signature/src/main/res/values/arrays.xml b/fake-signature/src/main/res/values/arrays.xml new file mode 100644 index 00000000..315cce58 --- /dev/null +++ b/fake-signature/src/main/res/values/arrays.xml @@ -0,0 +1,15 @@ + + + + + com.google.android.gms + com.android.vending + com.spotify.music + + + com.truecaller + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values/signature.xml b/fake-signature/src/main/res/values/signature.xml old mode 100755 new mode 100644 similarity index 56% rename from play-services-core/src/main/res/values/signature.xml rename to fake-signature/src/main/res/values/signature.xml index df53f65c..282e0dc1 --- a/play-services-core/src/main/res/values/signature.xml +++ b/fake-signature/src/main/res/values/signature.xml @@ -1,21 +1,14 @@ - - + 308204433082032ba003020102020900c2e08746644a308d300d06092a864886f70d01010405003074310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726f6964301e170d3038303832313233313333345a170d3336303130373233313333345a3074310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726f696430820120300d06092a864886f70d01010105000382010d00308201080282010100ab562e00d83ba208ae0a966f124e29da11f2ab56d08f58e2cca91303e9b754d372f640a71b1dcb130967624e4656a7776a92193db2e5bfb724a91e77188b0e6a47a43b33d9609b77183145ccdf7b2e586674c9e1565b1f4c6a5955bff251a63dabf9c55c27222252e875e4f8154a645f897168c0b1bfc612eabf785769bb34aa7984dc7e2ea2764cae8307d8c17154d7ee5f64a51a44a602c249054157dc02cd5f5c0e55fbef8519fbe327f0b1511692c5a06f19d18385f5c4dbc2d6b93f68cc2979c70e18ab93866b3bd5db8999552a0e3b4c99df58fb918bedc182ba35e003c1b4b10dd244a8ee24fffd333872ab5221985edab0fc0d0b145b6aa192858e79020103a381d93081d6301d0603551d0e04160414c77d8cc2211756259a7fd382df6be398e4d786a53081a60603551d2304819e30819b8014c77d8cc2211756259a7fd382df6be398e4d786a5a178a4763074310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c6520496e632e3110300e060355040b1307416e64726f69643110300e06035504031307416e64726f6964820900c2e08746644a308d300c0603551d13040530030101ff300d06092a864886f70d010104050003820101006dd252ceef85302c360aaace939bcff2cca904bb5d7a1661f8ae46b2994204d0ff4a68c7ed1a531ec4595a623ce60763b167297a7ae35712c407f208f0cb109429124d7b106219c084ca3eb3f9ad5fb871ef92269a8be28bf16d44c8d9a08e6cb2f005bb3fe2cb96447e868e731076ad45b33f6009ea19c161e62641aa99271dfd5228c5c587875ddb7f452758d661f6cc0cccb7352e424cc4365c523532f7325137593c4ae341f4db41edda0d0b1071a7c440f0fe9ea01cb627ca674369d084bd2fd911ff06cdbf2cfa10dc0f893ae35762919048c7efc64c7144178342f70581c9de573af55b390dd7fdb9418631895d5f759f30112687ff621410c069308a + + 308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37 + \ No newline at end of file diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 60485c49..f9797a58 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -23,6 +23,8 @@ dependencies { implementation "de.hdodenhof:circleimageview:$circleimageviewVersion" implementation "androidx.webkit:webkit:$webkitVersion" + implementation project(':fake-signature') + implementation project(':play-services-base-core') implementation project(':play-services-core-proto') implementation project(':play-services-core:microg-ui-tools') // deprecated diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index d4d1998d..84de96a6 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -100,10 +100,6 @@ - - Date: Mon, 25 Dec 2023 21:22:17 +0900 Subject: [PATCH 20/63] Checkin: Use null serial instead of empty https://github.com/microg/GmsCore/commit/ae607f166567c3c7acb423380064779ba135a677 --- .../src/main/java/org/microg/gms/checkin/CheckinClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java index 98598a09..84054bd2 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java @@ -137,7 +137,7 @@ public class CheckinClient { .loggingId(new Random().nextLong()) // TODO: static .meid(deviceIdent.meid) .otaCert(Collections.singletonList("71Q6Rn2DDZl1zPDVaaeEHItd")) - .serial(Build.SERIAL) + .serial(Build.SERIAL != null && !Build.SERIAL.isEmpty() ? Build.SERIAL : null) .timeZone(TimeZone.getDefault().getID()) .userName((String) TODO) .userSerialNumber((Integer) TODO) From caa7e931eb71405aa26f6fe3c457dc69aeef7a63 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Mon, 25 Dec 2023 21:31:00 +0900 Subject: [PATCH 21/63] Base: Allow default settings to be provided in meta-data https://github.com/microg/GmsCore/commit/2c80de55d132a91c25036880517c5605c6753f71 --- .../mgms/settings/MetaDataPreferences.kt | 45 +++++++++++++++++++ .../microg/mgms/settings/SettingsProvider.kt | 14 +++++- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 play-services-base-core/src/main/kotlin/org/microg/mgms/settings/MetaDataPreferences.kt diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/MetaDataPreferences.kt b/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/MetaDataPreferences.kt new file mode 100644 index 00000000..7187a34e --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/MetaDataPreferences.kt @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.mgms.settings + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.PackageManager.GET_META_DATA +import android.os.Bundle + +class MetaDataPreferences(private val context: Context, private val prefix: String = "") : SharedPreferences { + private val metaData by lazy { + runCatching { context.packageManager.getApplicationInfo(context.packageName, GET_META_DATA) }.getOrNull()?.metaData ?: Bundle.EMPTY + } + + override fun getAll(): Map = metaData.keySet().filter { it.startsWith(prefix) }.associate { it.substring(prefix.length) to metaData.get(it) } + + override fun getString(key: String, defValue: String?): String? = metaData.getString(prefix + key, defValue) + + override fun getStringSet(key: String, defValues: Set?): Set? = metaData.getStringArray(prefix + key)?.toSet() ?: defValues + + override fun getInt(key: String?, defValue: Int): Int = metaData.getInt(prefix + key, defValue) + + override fun getLong(key: String?, defValue: Long): Long = metaData.getLong(prefix + key, defValue) + + override fun getFloat(key: String?, defValue: Float): Float = metaData.getFloat(prefix + key, defValue) + + override fun getBoolean(key: String?, defValue: Boolean): Boolean = metaData.getBoolean(prefix + key, defValue) + + override fun contains(key: String?): Boolean = metaData.containsKey(prefix + key) + + override fun edit(): SharedPreferences.Editor { + throw UnsupportedOperationException() + } + + override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { + throw UnsupportedOperationException() + } + + override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { + throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt index eea6c2aa..b29e536f 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt @@ -24,6 +24,8 @@ import org.microg.mgms.settings.SettingsContract.Profile import org.microg.mgms.settings.SettingsContract.getAuthority import java.io.File +private const val SETTINGS_PREFIX = "org.microg.mgms.settings." + /** * All settings access should go through this [ContentProvider], * because it provides safe access from different processes which normal [SharedPreferences] don't. @@ -49,6 +51,10 @@ class SettingsProvider : ContentProvider() { } } + private val metaDataPreferences: SharedPreferences by lazy { + MetaDataPreferences(context!!, SETTINGS_PREFIX) + } + override fun onCreate(): Boolean { return true } @@ -272,10 +278,16 @@ class SettingsProvider : ContentProvider() { * @return the current setting as [Int], because [ContentProvider] does not support [Boolean]. */ private fun getSettingsBoolean(key: String, def: Boolean): Int { - return listOf(preferences, systemDefaultPreferences).getBooleanAsInt(key, def) + return listOf(preferences, systemDefaultPreferences, metaDataPreferences).getBooleanAsInt(key, def) } +<<<<<<< HEAD private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences).getString(key, def) +======= + private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getString(key, def) + private fun getSettingsInt(key: String, def: Int): Int = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getInt(key, def) + private fun getSettingsLong(key: String, def: Long): Long = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getLong(key, def) +>>>>>>> d2450feb (Base: Allow default settings to be provided in meta-data https://github.com/microg/GmsCore/commit/2c80de55d132a91c25036880517c5605c6753f71) private fun List.getString(key: String, def: String?): String? = foldRight(def) { preferences, defValue -> preferences?.getString(key, defValue) ?: defValue } private fun List.getBoolean(key: String, def: Boolean): Boolean = foldRight(def) { preferences, defValue -> preferences?.getBoolean(key, defValue) ?: defValue } From 3b12bb0e11442a5912d53ca7b4a2c055d4acd861 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 27 Dec 2023 05:04:07 +0900 Subject: [PATCH 22/63] Build: Change system for optional components https://github.com/microg/GmsCore/commit/dd8c5601f5a521fbf48a2bdcaf4b5ccc13340753 --- .../src/main/AndroidManifest.xml | 2 +- .../org/microg/gms/profile/ProfileManager.kt | 4 +- .../settings/MetaDataPreferences.kt | 2 +- .../settings/SettingsContract.kt | 2 +- .../settings/SettingsProvider.kt | 14 ++-- .../src/main/AndroidManifest.xml | 4 +- .../java/org/microg/gms/auth/AuthManager.java | 2 +- .../settings/GoogleSettingsProvider.java | 2 +- .../kotlin/org/microg/gms/auth/AuthPrefs.kt | 4 +- .../microg/gms/auth/appcert/AppCertManager.kt | 4 +- .../org/microg/gms/checkin/CheckinPrefs.kt | 6 +- .../org/microg/gms/checkin/LastCheckinInfo.kt | 4 +- .../org/microg/gms/checkin/ServiceInfo.kt | 6 +- .../kotlin/org/microg/gms/gcm/GcmPrefs.kt | 6 +- .../ui/PushNotificationAdvancedFragment.kt | 2 +- .../org/microg/gms/ui/SettingsFragment.kt | 72 ++++++++++++++++--- .../gms/ui/settings/SettingsProvider.kt | 58 +++++++++++++++ .../src/main/res/xml/preferences_start.xml | 4 +- 18 files changed, 154 insertions(+), 44 deletions(-) rename play-services-base-core/src/main/kotlin/org/microg/{mgms => gms}/settings/MetaDataPreferences.kt (98%) rename play-services-base-core/src/main/kotlin/org/microg/{mgms => gms}/settings/SettingsContract.kt (99%) rename play-services-base-core/src/main/kotlin/org/microg/{mgms => gms}/settings/SettingsProvider.kt (96%) rename play-services-core/src/main/java/org/microg/{mgms => gms}/settings/GoogleSettingsProvider.java (97%) create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt diff --git a/play-services-base-core/src/main/AndroidManifest.xml b/play-services-base-core/src/main/AndroidManifest.xml index 6585028c..be78c54f 100644 --- a/play-services-base-core/src/main/AndroidManifest.xml +++ b/play-services-base-core/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt index 8cb1afd2..bad9447a 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt @@ -10,8 +10,8 @@ import android.content.Context import android.content.res.XmlResourceParser import android.util.Log import org.microg.gms.utils.FileXmlResourceParser -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.Profile +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.Profile import org.xmlpull.v1.XmlPullParser import java.io.File import java.util.* diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/MetaDataPreferences.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt similarity index 98% rename from play-services-base-core/src/main/kotlin/org/microg/mgms/settings/MetaDataPreferences.kt rename to play-services-base-core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt index 7187a34e..1e21f6d9 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/MetaDataPreferences.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.mgms.settings +package org.microg.gms.settings import android.content.Context import android.content.SharedPreferences diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsContract.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt similarity index 99% rename from play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsContract.kt rename to play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 4f930962..9249b449 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsContract.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -1,4 +1,4 @@ -package org.microg.mgms.settings +package org.microg.gms.settings import android.content.ContentValues import android.content.Context diff --git a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt similarity index 96% rename from play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt rename to play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index b29e536f..5da489f0 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/mgms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -4,7 +4,7 @@ */ @file:Suppress("DEPRECATION") -package org.microg.mgms.settings +package org.microg.gms.settings import android.annotation.SuppressLint import android.content.ContentProvider @@ -17,14 +17,14 @@ import android.database.MatrixCursor import android.net.Uri import android.preference.PreferenceManager import org.microg.gms.common.PackageUtils.warnIfNotMainProcess -import org.microg.mgms.settings.SettingsContract.Auth -import org.microg.mgms.settings.SettingsContract.CheckIn -import org.microg.mgms.settings.SettingsContract.Gcm -import org.microg.mgms.settings.SettingsContract.Profile -import org.microg.mgms.settings.SettingsContract.getAuthority +import org.microg.gms.settings.SettingsContract.Auth +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.Gcm +import org.microg.gms.settings.SettingsContract.Profile +import org.microg.gms.settings.SettingsContract.getAuthority import java.io.File -private const val SETTINGS_PREFIX = "org.microg.mgms.settings." +private const val SETTINGS_PREFIX = "org.microg.gms.settings." /** * All settings access should go through this [ContentProvider], diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 84de96a6..6aac67eb 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -103,7 +103,7 @@ @@ -114,7 +114,7 @@ android:authorities="${basePackageName}.android.gsf.gservices" android:exported="true" /> diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java index 1cf5df87..57b6a516 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManager.java @@ -29,7 +29,7 @@ import android.util.Log; import org.microg.gms.base.core.BuildConfig; import org.microg.gms.common.PackageUtils; -import org.microg.mgms.settings.SettingsContract; +import org.microg.gms.settings.SettingsContract; import java.io.IOException; diff --git a/play-services-core/src/main/java/org/microg/mgms/settings/GoogleSettingsProvider.java b/play-services-core/src/main/java/org/microg/gms/settings/GoogleSettingsProvider.java similarity index 97% rename from play-services-core/src/main/java/org/microg/mgms/settings/GoogleSettingsProvider.java rename to play-services-core/src/main/java/org/microg/gms/settings/GoogleSettingsProvider.java index 3ab7f51c..da3e5094 100644 --- a/play-services-core/src/main/java/org/microg/mgms/settings/GoogleSettingsProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/settings/GoogleSettingsProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.microg.mgms.settings; +package org.microg.gms.settings; import android.content.ContentProvider; import android.content.ContentValues; diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt index a86db122..00a124aa 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt @@ -1,8 +1,8 @@ package org.microg.gms.auth import android.content.Context -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.Auth +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.Auth object AuthPrefs { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt index 7ae44173..3dee8489 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt @@ -28,8 +28,8 @@ import org.microg.gms.gcm.RegisterRequest import org.microg.gms.gcm.completeRegisterRequest import org.microg.gms.profile.Build import org.microg.gms.profile.ProfileManager -import org.microg.mgms.settings.SettingsContract.CheckIn -import org.microg.mgms.settings.SettingsContract.getSettings +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.getSettings import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec import kotlin.random.Random diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt index 019ddfa9..d14bc61b 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt @@ -5,9 +5,9 @@ package org.microg.gms.checkin import android.content.Context -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.CheckIn -import org.microg.mgms.settings.SettingsContract.setSettings +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.setSettings object CheckinPrefs { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt index e3e26f68..ea607093 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt @@ -16,8 +16,8 @@ package org.microg.gms.checkin import android.content.Context -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.CheckIn data class LastCheckinInfo( val lastCheckin: Long, diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt index 67084e03..7459d4bd 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt @@ -9,9 +9,9 @@ import android.content.Context import android.content.Intent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.microg.mgms.settings.SettingsContract.CheckIn -import org.microg.mgms.settings.SettingsContract.getSettings -import org.microg.mgms.settings.SettingsContract.setSettings +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.getSettings +import org.microg.gms.settings.SettingsContract.setSettings import java.io.Serializable data class ServiceInfo(val configuration: ServiceConfiguration, val lastCheckin: Long, val androidId: Long) : Serializable diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt index 23e551be..7b0046e8 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt @@ -8,9 +8,9 @@ import android.net.ConnectivityManager import android.net.NetworkInfo import android.util.Log import org.microg.gms.gcm.TriggerReceiver.FORCE_TRY_RECONNECT -import org.microg.mgms.settings.SettingsContract -import org.microg.mgms.settings.SettingsContract.Gcm -import org.microg.mgms.settings.SettingsContract.setSettings +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.Gcm +import org.microg.gms.settings.SettingsContract.setSettings import kotlin.math.max import kotlin.math.min diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt index 37bda799..55b730f6 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt @@ -14,7 +14,7 @@ import com.mgoogle.android.gms.R import org.microg.gms.gcm.GcmPrefs import org.microg.gms.gcm.getGcmServiceInfo import org.microg.gms.gcm.setGcmServiceConfiguration -import org.microg.mgms.settings.SettingsProvider +import org.microg.gms.settings.SettingsProvider class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { private lateinit var networkMobile: ListPreference diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index 8d503a68..4cb5b9df 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -8,21 +8,25 @@ package org.microg.gms.ui import android.content.ComponentName -import android.content.Context import android.content.pm.PackageManager import android.os.Bundle +import android.util.Log import androidx.preference.Preference +import androidx.preference.PreferenceCategory import androidx.preference.SwitchPreferenceCompat import com.mgoogle.android.gms.R import org.microg.gms.checkin.CheckinPrefs import org.microg.gms.gcm.GcmDatabase -import org.microg.gms.gcm.getGcmServiceInfo -import org.microg.mgms.settings.SettingsContract +import org.microg.gms.gcm.GcmPrefs +import org.microg.gms.settings.SettingsContract +import org.microg.gms.ui.settings.SettingsProvider +import org.microg.gms.ui.settings.getAllSettingsProviders import org.microg.tools.ui.ResourceSettingsFragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController class SettingsFragment : ResourceSettingsFragment() { + private val createdPreferences = mutableListOf() override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { super.onCreatePreferences(savedInstanceState, rootKey) @@ -48,19 +52,51 @@ class SettingsFragment : ResourceSettingsFragment() { } } + + for (entry in getAllSettingsProviders(requireContext()).flatMap { it.getEntriesStatic(requireContext()) }) { + entry.createPreference() + } + } + + private fun SettingsProvider.Companion.Entry.createPreference(): Preference? { + val preference = Preference(requireContext()).fillFromEntry(this) + try { + if (findPreference(when (group) { + SettingsProvider.Companion.Group.HEADER -> "prefcat_header" + SettingsProvider.Companion.Group.GOOGLE -> "prefcat_google_services" + SettingsProvider.Companion.Group.OTHER -> "prefcat_other_services" + SettingsProvider.Companion.Group.FOOTER -> "prefcat_footer" + })?.addPreference(preference) == true) { + createdPreferences.add(preference) + return preference + } else { + Log.w(TAG, "Preference not added $key") + } + } catch (e: Exception) { + Log.w(TAG, "Failed adding preference $key", e) + } + return null + } + + private fun Preference.fillFromEntry(entry: SettingsProvider.Companion.Entry): Preference { + key = entry.key + title = entry.title + summary = entry.summary + icon = entry.icon + isPersistent = false + isVisible = true + setOnPreferenceClickListener { + findNavController().navigate(requireContext(), entry.navigationId) + true + } + return this } override fun onResume() { super.onResume() val context = requireContext() - lifecycleScope.launchWhenResumed { - updateDetails(context) - } - } - private suspend fun updateDetails(context: Context) { - val gcmServiceInfo = getGcmServiceInfo(context) - if (gcmServiceInfo.configuration.enabled) { + if (GcmPrefs.get(requireContext()).isEnabled) { val database = GcmDatabase(context) val regCount = database.registrationList.size database.close() @@ -69,7 +105,23 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_GCM)?.setSummary(R.string.service_status_disabled_short) } +<<<<<<< HEAD findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPrefs.isEnabled(context) == true) R.string.service_status_enabled_short else R.string.service_status_disabled_short) +======= + findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPrefs.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + + lifecycleScope.launchWhenResumed { + val entries = getAllSettingsProviders(requireContext()).flatMap { it.getEntriesDynamic(requireContext()) } + for (preference in createdPreferences) { + if (!entries.any { it.key == preference.key }) preference.isVisible = false + } + for (entry in entries) { + val preference = createdPreferences.find { it.key == entry.key } + if (preference != null) preference.fillFromEntry(entry) + else entry.createPreference() + } + } +>>>>>>> 4018b660 (Build: Change system for optional components https://github.com/microg/GmsCore/commit/dd8c5601f5a521fbf48a2bdcaf4b5ccc13340753) } companion object { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt new file mode 100644 index 00000000..ecebb042 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.ui.settings + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.util.Log +import androidx.navigation.NavController + +private const val TAG = "SettingsProvider" + +interface SettingsProvider { + fun getEntriesStatic(context: Context): List + suspend fun getEntriesDynamic(context: Context): List = getEntriesStatic(context) + + fun preProcessSettingsIntent(intent: Intent) + + fun extendNavigation(navController: NavController) + + companion object { + enum class Group { + HEADER, + GOOGLE, + OTHER, + FOOTER + } + + data class Entry( + val key: String, + val group: Group, + val navigationId: Int, + val title: String, + val summary: String? = null, + val icon: Drawable? = null, + ) + } +} + +fun getAllSettingsProviders(context: Context): List { + val metaData = runCatching { context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA).metaData }.getOrNull() ?: Bundle.EMPTY + return metaData.keySet().asSequence().filter { + it.startsWith("org.microg.gms.ui.settings.entry:") + }.mapNotNull { + runCatching { metaData.getString(it) }.onFailure { Log.w(TAG, it) }.getOrNull() + }.mapNotNull { + runCatching { Class.forName(it) }.onFailure { Log.w(TAG, it) }.getOrNull() + }.filter { + SettingsProvider::class.java.isAssignableFrom(it) + }.mapNotNull { + runCatching { it.getDeclaredField("INSTANCE").get(null) as SettingsProvider }.onFailure { Log.w(TAG, it) }.getOrNull() + }.toList() +} \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/preferences_start.xml b/play-services-core/src/main/res/xml/preferences_start.xml index 5db2a913..5cef1aaf 100755 --- a/play-services-core/src/main/res/xml/preferences_start.xml +++ b/play-services-core/src/main/res/xml/preferences_start.xml @@ -16,7 +16,7 @@ - + - + From ebb9f2d54e44230536c88adf9fc3fd60fc3e051d Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 27 Dec 2023 05:11:45 +0900 Subject: [PATCH 23/63] Auth: Add option to authenticate without android id https://github.com/microg/GmsCore/commit/8663e52563040bfcc57432173f891108fa693f07 --- .../microg/gms/settings/SettingsContract.kt | 2 ++ .../microg/gms/settings/SettingsProvider.kt | 2 ++ .../java/org/microg/gms/auth/AuthRequest.java | 4 +++- .../kotlin/org/microg/gms/auth/AuthPrefs.kt | 7 +++++++ .../src/main/res/values/strings.xml | 2 ++ .../src/main/res/xml/preferences_account.xml | 20 +++++++++++++++++++ 6 files changed, 36 insertions(+), 1 deletion(-) diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 9249b449..bf0d7042 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -78,10 +78,12 @@ object SettingsContract { const val TRUST_GOOGLE = "auth_manager_trust_google" const val VISIBLE = "auth_manager_visible" + const val INCLUDE_ANDROID_ID = "auth_include_android_id" val PROJECTION = arrayOf( TRUST_GOOGLE, VISIBLE, + INCLUDE_ANDROID_ID, ) } diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index 5da489f0..9a5a0e31 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -213,6 +213,7 @@ class SettingsProvider : ContentProvider() { when (key) { Auth.TRUST_GOOGLE -> getSettingsBoolean(key, true) Auth.VISIBLE -> getSettingsBoolean(key, false) + Auth.INCLUDE_ANDROID_ID -> getSettingsBoolean(key, true) else -> throw IllegalArgumentException("Unknown key: $key") } } @@ -224,6 +225,7 @@ class SettingsProvider : ContentProvider() { when (key) { Auth.TRUST_GOOGLE -> editor.putBoolean(key, value as Boolean) Auth.VISIBLE -> editor.putBoolean(key, value as Boolean) + Auth.INCLUDE_ANDROID_ID -> editor.putBoolean(key, value as Boolean) else -> throw IllegalArgumentException("Unknown key: $key") } } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java index 55ff536b..e1b4749a 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java @@ -113,7 +113,9 @@ public class AuthRequest extends HttpFormClient.Request { public AuthRequest fromContext(Context context) { build(context); locale(Utils.getLocale(context)); - androidIdHex = Long.toHexString(LastCheckinInfo.read(context).getAndroidId()); + if (AuthPrefs.shouldIncludeAndroidId(context)) { + androidIdHex = Long.toHexString(LastCheckinInfo.read(context).getAndroidId()); + } return this; } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt index 00a124aa..5df2669d 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt @@ -24,4 +24,11 @@ object AuthPrefs { } } + @JvmStatic + fun shouldIncludeAndroidId(context: Context): Boolean { + return SettingsContract.getSettings(context, Auth.getContentUri(context), arrayOf(Auth.INCLUDE_ANDROID_ID)) { c -> + c.getInt(0) != 0 + } + } + } diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index 1c836304..c27278b1 100755 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -82,6 +82,8 @@ This can take a couple of minutes." Personal info & privacy Sign-in & security + Authenticate with device registration + When disabled, authentication requests won\'t be linked to the device registration, which allows unauthorized devices to sign in, but may have unforeseen consequences. Trust Google for app permissions When disabled, the user is asked before an apps authorization request is sent to Google. Some applications will fail to use the Google account if this is disabled. Allow apps to find accounts diff --git a/play-services-core/src/main/res/xml/preferences_account.xml b/play-services-core/src/main/res/xml/preferences_account.xml index c952b6e0..1f24d32e 100755 --- a/play-services-core/src/main/res/xml/preferences_account.xml +++ b/play-services-core/src/main/res/xml/preferences_account.xml @@ -11,6 +11,7 @@ android:targetPackage="com.android.settings" /> +<<<<<<< HEAD +======= + + + + + +>>>>>>> c8d53d6b (Auth: Add option to authenticate without android id https://github.com/microg/GmsCore/commit/8663e52563040bfcc57432173f891108fa693f07) \ No newline at end of file From f933a77f3f8f1027fcdf058cdebacc0f349b8f6d Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 27 Dec 2023 05:25:05 +0900 Subject: [PATCH 24/63] Auth: Forward UID/PID from IAuthManagerService.getTokenWithAccount https://github.com/microg/GmsCore/commit/4a7bac35f8eeb1bb3cb1607dc03b439b819abe40 --- .../android/auth/IAuthManagerService.aidl | 4 ++-- ...quest.aidl => HasCapabilitiesRequest.aidl} | 2 +- .../gms/auth/HasCababilitiesRequest.java | 16 -------------- .../gms/auth/HasCapabilitiesRequest.java | 22 +++++++++++++++++++ .../gms/auth/AuthManagerServiceImpl.java | 18 ++++++++++++--- 5 files changed, 40 insertions(+), 22 deletions(-) rename play-services-api/src/main/aidl/com/google/android/gms/auth/{HasCababilitiesRequest.aidl => HasCapabilitiesRequest.aidl} (52%) delete mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.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 ae33a334..d6afdf80 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 @@ -7,7 +7,7 @@ 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; +import com.google.android.gms.auth.HasCapabilitiesRequest; interface IAuthManagerService { Bundle getToken(String accountName, String scope, in Bundle extras) = 0; @@ -18,6 +18,6 @@ interface IAuthManagerService { Bundle getAccounts(in Bundle extras) = 5; Bundle removeAccount(in Account account) = 6; Bundle requestGoogleAccountsAccess(String packageName) = 7; - int hasCapabilities(in HasCababilitiesRequest request) = 8; + int hasCapabilities(in HasCapabilitiesRequest 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/HasCababilitiesRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCapabilitiesRequest.aidl similarity index 52% rename from play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl rename to play-services-api/src/main/aidl/com/google/android/gms/auth/HasCapabilitiesRequest.aidl index c0c080cd..b6cb6b9d 100644 --- 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/HasCapabilitiesRequest.aidl @@ -1,3 +1,3 @@ package com.google.android.gms.auth; -parcelable HasCababilitiesRequest; +parcelable HasCapabilitiesRequest; 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 deleted file mode 100644 index c9907fa8..00000000 --- a/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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; - public static final Creator CREATOR = new AutoCreator<>(HasCababilitiesRequest.class); -} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java new file mode 100644 index 00000000..44f532a0 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java @@ -0,0 +1,22 @@ +/* + * 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 HasCapabilitiesRequest extends AutoSafeParcelable { + @Field(1) + public Account account; +<<<<<<< HEAD:play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java + public static final Creator CREATOR = new AutoCreator<>(HasCababilitiesRequest.class); +======= + @Field(2) + public String[] capabilities; + public static final Creator CREATOR = new AutoCreator<>(HasCapabilitiesRequest.class); +>>>>>>> d04e2718 (Auth: Forward UID/PID from IAuthManagerService.getTokenWithAccount https://github.com/microg/GmsCore/commit/4a7bac35f8eeb1bb3cb1607dc03b439b819abe40):play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java +} 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 58e26a13..5093bbe6 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 @@ -37,7 +37,7 @@ 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.HasCapabilitiesRequest; import com.google.android.gms.auth.TokenData; import com.google.android.gms.common.api.Scope; @@ -68,6 +68,8 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { public static final String KEY_REQUEST_VISIBLE_ACTIVITIES = "request_visible_actions"; public static final String KEY_SUPPRESS_PROGRESS_SCREEN = "suppressProgressScreen"; public static final String KEY_SYNC_EXTRAS = "sync_extras"; + public static final String KEY_DELEGATION_TYPE = "delegation_type"; + public static final String KEY_DELEGATEE_USER_ID = "delegatee_user_id"; public static final String KEY_ERROR = "Error"; @@ -113,7 +115,8 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0)); boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false); - Log.d(TAG, "getToken: account:" + account.name + " scope:" + scope + " extras:" + extras + ", notify: " + notify); + if (!AuthConstants.SCOPE_GET_ACCOUNT_ID.equals(scope)) + Log.d(TAG, "getToken: account:" + account.name + " scope:" + scope + " extras:" + extras + ", notify: " + notify); /* * TODO: This scope seems to be invalid (according to https://developers.google.com/oauthplayground/), @@ -196,7 +199,12 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override +<<<<<<< HEAD public int hasCapabilities(HasCababilitiesRequest request) { +======= + public int hasCapabilities(HasCapabilitiesRequest request) throws RemoteException { + Log.w(TAG, "Not implemented: hasCapabilities(" + request.account + ", " + Arrays.toString(request.capabilities) + ")"); +>>>>>>> d04e2718 (Auth: Forward UID/PID from IAuthManagerService.getTokenWithAccount https://github.com/microg/GmsCore/commit/4a7bac35f8eeb1bb3cb1607dc03b439b819abe40) return 1; } @@ -215,7 +223,11 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { Log.d(TAG, "clearToken: token:" + token + " extras:" + extras); AccountManager.get(context).invalidateAuthToken(AuthConstants.DEFAULT_ACCOUNT_TYPE, token); - return null; + + Bundle res = new Bundle(); + res.putString("Error", "Ok"); + res.putBoolean("booleanResult", true); + return res; } @Override From ae639e3771c3d6d87fc8046fb8833342a2dc7b20 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 27 Dec 2023 05:30:04 +0900 Subject: [PATCH 25/63] Declare com.mgoogle.android.providers.gsf.permission.READ_GSERVICES permission https://github.com/microg/GmsCore/commit/833e2940f283eeda5eca23b3af898dbfbab24d57 --- play-services-core/src/main/AndroidManifest.xml | 7 +++++++ play-services-core/src/main/res/values/strings.xml | 1 + 2 files changed, 8 insertions(+) diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 6aac67eb..77172b79 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ xmlns:tools="http://schemas.android.com/tools"> + >>>>>> caf7201f (Declare com.mgoogle.android.providers.gsf.permission.READ_GSERVICES permission https://github.com/microg/GmsCore/commit/833e2940f283eeda5eca23b3af898dbfbab24d57) android:name="${basePackageName}.android.c2dm.permission.RECEIVE" >>>>>>> 25329625 (Set basePackageName) android:label="@string/perm_c2dm_receive_label" diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index c27278b1..c08c652d 100755 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ This can take a couple of minutes." read Google service configuration listen to internal status broadcasts + read Google service configuration listen to C2DM messages send C2DM messages to other apps exchange messages and receive sync notifications from Google servers From 8ccec790c1ae1df3b9ea09adc73054e6c2608937 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 27 Dec 2023 06:20:25 +0900 Subject: [PATCH 26/63] Add an activity-alias for Google settings compatibility (other apps are supposed to able to open Google settings) https://github.com/microg/GmsCore/commit/af5055034eabe72bffe41a62b2ffe8f3dd52aabc --- play-services-core/src/main/AndroidManifest.xml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 77172b79..4b4c1489 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -382,6 +382,15 @@ android:resource="@string/gms_settings_summary" /> + + + + - - - - From 77cef2fec3a0d01c2ebefed84466bbf46a427f52 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 27 Dec 2023 06:29:46 +0900 Subject: [PATCH 27/63] Expose device profile as content https://github.com/microg/GmsCore/commit/9d5a99710f0cbe9055a43df371eaae77610583f3 --- .../src/main/AndroidManifest.xml | 12 +++++ .../org/microg/gms/profile/ProfileProvider.kt | 52 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileProvider.kt diff --git a/play-services-base-core/src/main/AndroidManifest.xml b/play-services-base-core/src/main/AndroidManifest.xml index be78c54f..f260e226 100644 --- a/play-services-base-core/src/main/AndroidManifest.xml +++ b/play-services-base-core/src/main/AndroidManifest.xml @@ -3,11 +3,23 @@ ~ SPDX-FileCopyrightText: 2020, microG Project Team ~ SPDX-License-Identifier: Apache-2.0 --> +<<<<<<< HEAD +======= + +>>>>>>> 0f8fb823 (Expose device profile as content https://github.com/microg/GmsCore/commit/9d5a99710f0cbe9055a43df371eaae77610583f3) + + diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileProvider.kt new file mode 100644 index 00000000..53e4149f --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileProvider.kt @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2023 e Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.profile + +import android.content.ContentProvider +import android.content.ContentValues +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import org.microg.gms.settings.SettingsContract + +class ProfileProvider : ContentProvider() { + + val COLUMN_ID = "profile_id" + val COLUMN_VALUE = "profile_value" + + override fun onCreate(): Boolean { + ProfileManager.ensureInitialized(context!!) + return true + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor = + MatrixCursor(arrayOf(COLUMN_ID, COLUMN_VALUE)).apply { + ProfileManager.getActiveProfileData(context!!).entries + .forEach { + addRow(arrayOf(it.key, it.value)) + } + } + + override fun getType(uri: Uri): String { + return "vnd.android.cursor.item/vnd.${SettingsContract.getAuthority(context!!)}.${uri.path}" + } + + override fun insert(uri: Uri, values: ContentValues?): Nothing = throw UnsupportedOperationException() + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Nothing = + throw UnsupportedOperationException() + + override fun update( + uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array? + ): Nothing = throw UnsupportedOperationException() + +} From f897156c311c0a4e4c236d4eb7d44d006208f2a2 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 27 Dec 2023 06:36:02 +0900 Subject: [PATCH 28/63] Build: Fix GetLocales lint https://github.com/microg/GmsCore/commit/f0b15d275a0c4ec3625033d651414d396e368320 --- .../gms/common/DeviceConfiguration.java | 19 ++++++++++++++----- play-services-core/build.gradle | 6 ++++++ .../microg-ui-tools/build.gradle | 7 +++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java b/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java index 1246a50c..7a57f39e 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java @@ -16,6 +16,8 @@ package org.microg.gms.common; +import android.annotation.SuppressLint; + import android.app.ActivityManager; import android.content.Context; import android.content.pm.ConfigurationInfo; @@ -90,17 +92,24 @@ public class DeviceConfiguration { this.nativePlatforms = getNativePlatforms(); widthPixels = displayMetrics.widthPixels; heightPixels = displayMetrics.heightPixels; - locales = new ArrayList<>(Arrays.asList(context.getAssets().getLocales())); - for (int i = 0; i < locales.size(); i++) { - locales.set(i, locales.get(i).replace("-", "_")); - } - Collections.sort(locales); + locales = getLocales(context); Set glExtensions = new HashSet<>(); addEglExtensions(glExtensions); this.glExtensions = new ArrayList<>(glExtensions); Collections.sort(this.glExtensions); } + @SuppressLint("GetLocales") + private static List getLocales(Context context) { + List locales = new ArrayList(); + locales.addAll(Arrays.asList(context.getAssets().getLocales())); + for (int i = 0; i < locales.size(); i++) { + locales.set(i, locales.get(i).replace("-", "_")); + } + Collections.sort(locales); + return locales; + } + @SuppressWarnings({"InlinedApi"}) private static List getNativePlatforms() { <<<<<<< HEAD diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index f9797a58..01d928ca 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -97,6 +97,12 @@ android { } } +<<<<<<< HEAD +======= + lintOptions { + disable 'MissingTranslation', 'GetLocales', 'BatteryLife', 'ImpliedQuantity', 'MissingQuantity', 'InvalidWakeLockTag', 'Range' + } +>>>>>>> a907c391 (Build: Fix GetLocales lint https://github.com/microg/GmsCore/commit/f0b15d275a0c4ec3625033d651414d396e368320) buildTypes { release { diff --git a/play-services-core/microg-ui-tools/build.gradle b/play-services-core/microg-ui-tools/build.gradle index 9b1df7b8..039e97f1 100644 --- a/play-services-core/microg-ui-tools/build.gradle +++ b/play-services-core/microg-ui-tools/build.gradle @@ -31,8 +31,15 @@ android { sourceCompatibility = 11 targetCompatibility = 11 } +<<<<<<< HEAD lint { disable 'MissingTranslation' +======= + + lintOptions { + // TODO: Remove MissingTranslation once we have stable strings and proper translations. + disable 'MissingTranslation', 'GetLocales' +>>>>>>> a907c391 (Build: Fix GetLocales lint https://github.com/microg/GmsCore/commit/f0b15d275a0c4ec3625033d651414d396e368320) } namespace 'org.microg.tools.ui' From 01b38f6ea1bdd9ed7743474d724e62b708514487 Mon Sep 17 00:00:00 2001 From: Cuynu Date: Fri, 10 May 2024 17:12:50 +0700 Subject: [PATCH 29/63] rebase --- build.gradle | 29 +-- gradle.properties | 6 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 61624 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 23 +- gradlew.bat | 34 +-- play-services-api/build.gradle | 10 +- .../src/main/AndroidManifest.xml | 18 ++ .../internal/GoogleCertificatesQuery.aidl | 3 - .../internal/IGoogleCertificatesApi.aidl | 12 - .../android/gms/auth/AccountChangeEvent.java | 2 +- .../gms/auth/AccountChangeEventsRequest.java | 16 +- .../gms/auth/AccountChangeEventsResponse.java | 11 +- .../gms/auth/GetHubTokenInternalResponse.java | 7 + .../android/gms/auth/GetHubTokenRequest.java | 2 + .../gms/auth/HasCapabilitiesRequest.java | 4 - .../google/android/gms/auth/TokenData.java | 4 +- .../gms/auth/api/credentials/Credential.java | 98 ++++++- .../credentials/CredentialPickerConfig.java | 62 +++-- .../api/credentials/CredentialRequest.java | 45 +++- .../gms/auth/api/credentials/HintRequest.java | 130 +++++++-- .../gms/auth/api/credentials/IdToken.java | 19 +- .../api/credentials/IdentityProviders.java | 19 ++ .../credentials/internal/DeleteRequest.java | 2 +- .../internal/GeneratePasswordRequest.java | 2 +- .../api/credentials/internal/SaveRequest.java | 2 +- .../gms/auth/api/proxy/ProxyRequest.java | 21 +- .../gms/auth/api/proxy/ProxyResponse.java | 15 ++ .../AccountRecoveryGuidanceRequest.java | 4 +- .../dataservice/AccountRemovalRequest.java | 2 + .../ConfirmCredentialsRequest.java | 12 +- .../firstparty/dataservice/TokenRequest.java | 4 +- .../firstparty/dataservice/TokenResponse.java | 20 ++ .../ConfirmCredentialsWorkflowRequest.java | 11 +- .../firstparty/proximity/data/Permit.java | 20 ++ .../firstparty/shared/AccountCredentials.java | 4 +- .../firstparty/shared/AppDescription.java | 13 +- .../firstparty/shared/CaptchaSolution.java | 6 +- .../common/server/FavaDiagnosticsEntity.java | 2 +- .../identity/accounts/api/AccountData.java | 20 ++ .../intents/model/CountrySpecification.java | 20 ++ .../identity/intents/model/UserAddress.java | 20 ++ .../internal/ParcelableLoadImageOptions.java | 14 +- .../gms/people/model/AccountMetadata.java | 18 +- .../gms/people/model/AvatarReference.java | 13 +- play-services-base-api/build.gradle | 4 - .../src/main/AndroidManifest.xml | 6 + .../android/gms/common/data/DataHolder.java | 213 ++++++++++++++- .../android/gms/common/data/Freezable.java | 39 +++ .../android/gms/common/images/WebImage.java | 6 +- play-services-base-core/build.gradle | 6 +- .../src/main/AndroidManifest.xml | 5 +- .../microg/gms/AbstractGmsServiceBroker.java | 30 ++- .../main/java/org/microg/gms/BaseService.java | 3 +- .../gms/common/DeviceConfiguration.java | 4 - .../gms/common/ForegroundServiceContext.java | 7 - .../org/microg/gms/common/PackageUtils.java | 19 +- .../microg/gms/common/PackageSpoofUtils.kt | 1 + .../kotlin/org/microg/gms/profile/Build.kt | 2 + .../org/microg/gms/profile/ProfileManager.kt | 24 +- .../microg/gms/settings/SettingsContract.kt | 12 +- .../microg/gms/settings/SettingsProvider.kt | 14 +- .../src/main/res/values-ja/strings.xml | 6 +- play-services-base/build.gradle | 5 +- .../src/main/AndroidManifest.xml | 3 +- .../gms/common/GoogleApiAvailability.java | 77 +++++- .../gms/common/GooglePlayServicesClient.java | 14 + .../gms/common/GooglePlayServicesUtil.java | 140 ++++++++-- .../google/android/gms/common/api/Api.java | 32 ++- .../android/gms/common/api/GoogleApi.java | 13 +- .../gms/common/api/GoogleApiActivity.java | 8 - .../gms/common/api/GoogleApiClient.java | 238 ++++++++++++++++- .../android/gms/common/api/HasApiKey.java | 5 +- .../android/gms/common/api/PendingResult.java | 4 - .../gms/common/api/internal/ApiKey.java | 6 +- .../android/gms/common/data/DataBuffer.java | 30 ++- .../common/ForwardConnectionCallbacks.java | 51 ++++ .../ForwardConnectionFailedListener.java | 47 ++++ .../gms/common/api/AbstractPendingResult.java | 88 ++++++- .../api/AbstractPlayServicesClient.java | 99 +++++++ .../gms/common/api/GoogleApiClientImpl.java | 68 ++--- .../gms/common/api/GoogleApiManager.java | 19 +- .../gms/common/api/InstantPendingResult.java | 61 +++++ .../gms/common/api/ResultCallbackHandler.java | 3 - play-services-basement/build.gradle | 10 +- .../src/main/AndroidManifest.xml | 18 ++ .../android/gms/common/ConnectionResult.aidl | 3 + .../common/GoogleCertificatesLookupQuery.aidl | 3 + .../GoogleCertificatesLookupResponse.aidl | 3 + .../gms/common/GoogleCertificatesQuery.aidl | 3 + .../gms/common/internal/ICertData.aidl | 0 .../internal/IGoogleCertificatesApi.aidl | 18 ++ .../common/internal/ISignInButtonCreator.aidl | 0 .../android/gms/common/ConnectionResult.java | 39 +++ .../common/GoogleCertificatesLookupQuery.java | 50 ++++ .../GoogleCertificatesLookupResponse.java | 38 +++ .../gms/common}/GoogleCertificatesQuery.java | 64 +++-- .../com/google/android/gms/common/Scopes.java | 22 ++ .../gms/common/api/CommonStatusCodes.java | 54 ++++ .../google/android/gms/common/api/Scope.java | 14 +- .../google/android/gms/common/api/Status.java | 83 ++++++ .../android/gms/common/internal/CertData.java | 4 +- .../android/gms/dynamic/ObjectWrapper.java | 32 +-- .../org/microg/gms/auth/AuthConstants.java | 1 + .../java/org/microg/gms/common/Constants.java | 5 - .../org/microg/gms/common/GmsService.java | 20 +- .../android/gms/dynamic/ObjectWrapper.kt | 6 +- play-services-core-proto/build.gradle | 4 +- play-services-core/build.gradle | 15 +- .../microg-ui-tools/build.gradle | 11 +- .../src/main/AndroidManifest.xml | 18 ++ .../tools/ui/AbstractAboutFragment.java | 8 +- .../tools/ui/AbstractDashboardActivity.java | 21 ++ .../tools/ui/AbstractSettingsActivity.java | 28 +- .../tools/ui/AbstractSettingsFragment.java | 4 +- .../java/org/microg/tools/ui/Condition.java | 87 +++++-- .../org/microg/tools/ui/DialogPreference.java | 14 +- .../microg/tools/ui/LongTextPreference.java | 52 ++++ .../tools/ui/RadioButtonPreference.java | 46 ++++ .../java/org/microg/tools/ui/SwitchBar.java | 34 +++ .../org/microg/tools/ui/ToggleSwitch.java | 4 + .../src/main/res/layout/switch_bar.xml | 1 - .../src/main/AndroidManifest.xml | 68 ++--- .../chimera/DynamiteModuleInitializer.java | 25 ++ .../gms/common/GoogleCertificatesImpl.java | 77 ------ .../gms/auth/AccountContentProvider.java | 9 - .../gms/auth/AuthManagerServiceImpl.java | 29 +-- .../gms/auth/login/AssistantActivity.java | 2 +- .../microg/gms/auth/login/LoginActivity.java | 20 +- .../org/microg/gms/checkin/CheckinClient.java | 3 +- .../microg/gms/checkin/CheckinManager.java | 3 +- .../java/org/microg/gms/gcm/McsService.java | 42 +-- .../gms/ui/LegacyAccountSettingsActivity.java | 2 +- .../org/microg/gms/ui/SettingsActivity.java | 4 +- .../gms/common/GoogleCertificatesImpl.kt | 90 +++++++ .../kotlin/org/microg/gms/auth/AuthPrefs.kt | 16 +- .../microg/gms/auth/appcert/AppCertManager.kt | 20 +- .../microg/gms/auth/proxy/AuthProxyService.kt | 7 +- .../org/microg/gms/checkin/CheckinPrefs.kt | 28 +- .../org/microg/gms/checkin/LastCheckinInfo.kt | 60 ++--- .../org/microg/gms/checkin/ServiceInfo.kt | 20 +- .../kotlin/org/microg/gms/gcm/GcmPrefs.kt | 100 ++----- .../org/microg/gms/gcm/PushRegisterService.kt | 44 ++-- .../kotlin/org/microg/gms/gcm/ServiceInfo.kt | 8 +- .../org/microg/gms/ui/AppIconPreference.kt | 3 +- .../ui/PushNotificationPreferencesFragment.kt | 4 +- .../org/microg/gms/ui/SettingsFragment.kt | 4 - .../org/microg/gms/ui/TextPreference.kt | 4 +- .../main/kotlin/org/microg/gms/ui/Utils.kt | 13 +- .../src/main/res/values/strings.xml | 1 - .../src/main/res/xml/preferences_account.xml | 68 ----- .../main/res/xml/preferences_google_more.xml | 66 ++++- .../src/main/res/xml/preferences_start.xml | 17 +- play-services-iid-api/build.gradle | 8 +- .../src/main/AndroidManifest.xml | 18 ++ .../android/gms/iid/MessengerCompat.java | 7 +- play-services-tasks/build.gradle | 5 +- .../src/main/AndroidManifest.xml | 7 + .../gms/tasks/TaskCompletionSource.java | 10 +- .../com/google/android/gms/tasks/Tasks.java | 246 +++++++++++++++++- .../java/org/microg/gms/tasks/TaskImpl.java | 3 +- settings.gradle | 5 +- 162 files changed, 3122 insertions(+), 1096 deletions(-) create mode 100644 play-services-api/src/main/AndroidManifest.xml delete mode 100644 play-services-api/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl delete mode 100644 play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl create mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenResponse.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/firstparty/proximity/data/Permit.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/identity/accounts/api/AccountData.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/identity/intents/model/CountrySpecification.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/identity/intents/model/UserAddress.java create mode 100644 play-services-base-api/src/main/AndroidManifest.xml create mode 100644 play-services-base-api/src/main/java/com/google/android/gms/common/data/Freezable.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionCallbacks.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionFailedListener.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/api/AbstractPlayServicesClient.java create mode 100644 play-services-base/src/main/java/org/microg/gms/common/api/InstantPendingResult.java create mode 100644 play-services-basement/src/main/AndroidManifest.xml create mode 100644 play-services-basement/src/main/aidl/com/google/android/gms/common/ConnectionResult.aidl create mode 100644 play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupQuery.aidl create mode 100644 play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupResponse.aidl create mode 100644 play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesQuery.aidl rename {play-services-api => play-services-basement}/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl (100%) create mode 100644 play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl rename {play-services-api => play-services-basement}/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl (100%) create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupQuery.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupResponse.java rename {play-services-api/src/main/java/com/google/android/gms/common/internal => play-services-basement/src/main/java/com/google/android/gms/common}/GoogleCertificatesQuery.java (53%) create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/Scopes.java rename {play-services-api => play-services-basement}/src/main/java/com/google/android/gms/common/internal/CertData.java (93%) create mode 100644 play-services-core/microg-ui-tools/src/main/AndroidManifest.xml create mode 100644 play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/LongTextPreference.java create mode 100644 play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/RadioButtonPreference.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/chimera/DynamiteModuleInitializer.java delete mode 100644 play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java create mode 100644 play-services-core/src/main/kotlin/com/google/android/gms/common/GoogleCertificatesImpl.kt delete mode 100755 play-services-core/src/main/res/xml/preferences_account.xml create mode 100644 play-services-iid-api/src/main/AndroidManifest.xml create mode 100644 play-services-tasks/src/main/AndroidManifest.xml diff --git a/build.gradle b/build.gradle index b9e5cae4..053dafeb 100644 --- a/build.gradle +++ b/build.gradle @@ -4,42 +4,35 @@ */ buildscript { -<<<<<<< HEAD -<<<<<<< HEAD - ext.kotlinVersion = '1.8.20' -======= -======= ext.applicationNamespace = "com.google.android.gms" ext.basePackageName = "com.mgoogle" ->>>>>>> 25329625 (Set basePackageName) ext.safeParcelVersion = '1.7.1' ext.kotlinVersion = '1.7.21' ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) ext.coroutineVersion = '1.6.4' - ext.annotationVersion = '1.6.0' + ext.annotationVersion = '1.5.0' ext.appcompatVersion = '1.6.1' ext.coreVersion = '1.9.0' ext.collectionVersion = '1.3.0-dev01' ext.fragmentVersion = '1.5.5' ext.lifecycleVersion = '2.5.1' - ext.materialVersion = '1.8.0' + ext.materialVersion = '1.7.0' ext.navigationVersion = '2.5.3' ext.preferenceVersion = '1.2.0' - ext.webkitVersion = '1.6.0' + ext.webkitVersion = '1.5.0' ext.circleimageviewVersion = '3.1.0' ext.volleyVersion = '1.2.1' - ext.wireVersion = '4.5.5' + ext.wireVersion = '4.8.0' - ext.androidBuildGradleVersion = '8.0.0' + ext.androidBuildGradleVersion = '7.4.2' ext.androidBuildVersionTools = '33.0.2' ext.androidMinSdk = 23 - ext.androidTargetSdk = 30 + ext.androidTargetSdk = 29 ext.androidCompileSdk = 33 repositories { @@ -48,14 +41,14 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.0.2' + classpath "com.android.tools.build:gradle:$androidBuildGradleVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "com.squareup.wire:wire-gradle-plugin:$wireVersion" } } -def gmsVersion = "24.09.13" +def gmsVersion = "23.49.14" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) def ourVersionBase = "0.3.0" @@ -77,9 +70,6 @@ subprojects { mavenCentral() google() } -<<<<<<< HEAD -} -======= afterEvaluate { // Temporary hack for Android Studio if (project.plugins.hasPlugin('com.android.base')) { @@ -89,5 +79,4 @@ subprojects { } } } -} ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 86630672..976747da 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,3 @@ android.useAndroidX=true -org.gradle.jvmargs=-Xmx4096m -XX:+UseParallelGC -android.defaults.buildfeatures.buildconfig=true -android.nonTransitiveRClass=false -android.nonFinalResIds=false \ No newline at end of file +android.disableAutomaticComponentCreation=true +org.gradle.jvmargs=-Xmx2048m -XX:+UseParallelGC diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..afba109285af78dbd2a1d187e33ac4f87c76e392 100644 GIT binary patch delta 39181 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 delta 36594 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�( zFNRRLr15z1Wa<04>4KDxy diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e736c04d..b1624c47 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jun 29 16:45:22 ICT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-rc-3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 60cf9e8c..65dcd68d 100755 --- a/gradlew +++ b/gradlew @@ -1,6 +1,5 @@ #!/bin/sh -# SPDX-License-Identifier: Apache-2.0 # # Copyright © 2015-2021 the original authors. # @@ -56,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -81,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -128,12 +127,14 @@ if [ -n "$JAVA_HOME" ] ; then fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi @@ -142,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -204,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. @@ -230,4 +241,4 @@ eval "set -- $( tr '\n' ' ' )" '"$@"' -exec "$JAVACMD" "$@" \ No newline at end of file +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 5093609d..93e3f59f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +65,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/play-services-api/build.gradle b/play-services-api/build.gradle index f78981e9..e254f48d 100644 --- a/play-services-api/build.gradle +++ b/play-services-api/build.gradle @@ -17,8 +17,8 @@ apply plugin: 'com.android.library' android { - compileSdk androidCompileSdk - buildToolsVersion '33.0.2' + compileSdkVersion androidCompileSdk + buildToolsVersion androidBuildVersionTools defaultConfig { minSdkVersion androidMinSdk @@ -27,11 +27,7 @@ android { compileOptions { sourceCompatibility = 1.8 - targetCompatibility = "1.8" - } - namespace 'org.microg.gms.api' - buildFeatures { - aidl true + targetCompatibility = 1.8 } } diff --git a/play-services-api/src/main/AndroidManifest.xml b/play-services-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d4a4d07a --- /dev/null +++ b/play-services-api/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl b/play-services-api/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl deleted file mode 100644 index ae02e068..00000000 --- a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl +++ /dev/null @@ -1,3 +0,0 @@ -package com.google.android.gms.common.internal; - -parcelable GoogleCertificatesQuery; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl b/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl deleted file mode 100644 index c75982bb..00000000 --- a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl +++ /dev/null @@ -1,12 +0,0 @@ -package com.google.android.gms.common.internal; - -import com.google.android.gms.common.internal.GoogleCertificatesQuery; -import com.google.android.gms.dynamic.IObjectWrapper; - -interface IGoogleCertificatesApi { - IObjectWrapper getGoogleCertificates(); - IObjectWrapper getGoogleReleaseCertificates(); - boolean isGoogleReleaseSigned(String packageName, IObjectWrapper certData); - boolean isGoogleSigned(String packageName, IObjectWrapper certData); - boolean isGoogleOrPlatformSigned(in GoogleCertificatesQuery query, IObjectWrapper packageManager); -} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEvent.java b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEvent.java index d9a19568..0485f3a2 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEvent.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEvent.java @@ -19,5 +19,5 @@ package com.google.android.gms.auth; import org.microg.safeparcel.AutoSafeParcelable; public class AccountChangeEvent extends AutoSafeParcelable { - public static Creator CREATOR = new AutoCreator<>(AccountChangeEvent.class); + public static Creator CREATOR = new AutoCreator(AccountChangeEvent.class); } 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 cf8561df..1d404288 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 @@ -21,18 +21,18 @@ 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 { + @Field(1) + private final int versionCode = 1; + @Field(2) + private int since; @Field(3) @Deprecated - private final String accountName; + private String accountName; @Field(4) - private final Account account; - - public AccountChangeEventsRequest(String accountName, Account account) { - this.accountName = accountName; - this.account = account; - } + private Account account; public Account getAccount() { if (account != null) return account; @@ -40,6 +40,6 @@ public class AccountChangeEventsRequest extends AutoSafeParcelable { return null; } - public static Creator CREATOR = new AutoCreator<>(AccountChangeEventsRequest.class); + public static Creator CREATOR = new AutoCreator(AccountChangeEventsRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsResponse.java index 10491379..763e5d42 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsResponse.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsResponse.java @@ -17,11 +17,20 @@ package com.google.android.gms.auth; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; + +import java.util.ArrayList; +import java.util.List; public class AccountChangeEventsResponse extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; + @SafeParceled(value = 2, subClass = AccountChangeEvent.class) + private final List events; public AccountChangeEventsResponse() { + events = new ArrayList(); } - public static Creator CREATOR = new AutoCreator<>(AccountChangeEventsResponse.class); + public static Creator CREATOR = new AutoCreator(AccountChangeEventsResponse.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 index 4845ed41..ccecf291 100644 --- 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 @@ -5,10 +5,17 @@ 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 index 2c4e973c..cdaffb51 100644 --- 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 @@ -14,5 +14,7 @@ public class GetHubTokenRequest extends AutoSafeParcelable { 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/HasCapabilitiesRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java index 44f532a0..e35ca6d7 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java @@ -12,11 +12,7 @@ import org.microg.safeparcel.AutoSafeParcelable; public class HasCapabilitiesRequest extends AutoSafeParcelable { @Field(1) public Account account; -<<<<<<< HEAD:play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java - public static final Creator CREATOR = new AutoCreator<>(HasCababilitiesRequest.class); -======= @Field(2) public String[] capabilities; public static final Creator CREATOR = new AutoCreator<>(HasCapabilitiesRequest.class); ->>>>>>> d04e2718 (Auth: Forward UID/PID from IAuthManagerService.getTokenWithAccount https://github.com/microg/GmsCore/commit/4a7bac35f8eeb1bb3cb1607dc03b439b819abe40):play-services-api/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/TokenData.java b/play-services-api/src/main/java/com/google/android/gms/auth/TokenData.java index df3768c8..c6fbe9e5 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/TokenData.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/TokenData.java @@ -24,6 +24,8 @@ import org.microg.safeparcel.SafeParceled; import java.util.List; public class TokenData extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; @SafeParceled(2) public final String token; @@ -58,5 +60,5 @@ public class TokenData extends AutoSafeParcelable { this.scopes = null; } - public static final Creator CREATOR = new AutoCreator<>(TokenData.class); + public static final Creator CREATOR = new AutoCreator(TokenData.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/Credential.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/Credential.java index 9ade5cc0..5a92ddc5 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/Credential.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/Credential.java @@ -9,17 +9,21 @@ package com.google.android.gms.auth.api.credentials; import android.net.Uri; +import android.text.TextUtils; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; import java.util.Arrays; import java.util.List; -import java.util.Objects; +import java.util.Locale; @PublicApi public class Credential extends AutoSafeParcelable { + @Field(1000) + private final int versionCode = 1; + @Field(1) private String id; @Field(2) @@ -51,6 +55,13 @@ public class Credential extends AutoSafeParcelable { return accountType; } + /** + * Returns the generated password for an account hint. + */ + public String getGeneratedPassword() { + return generatedPassword; + } + /** * Returns the credential identifier, typically an email address or user name, though it may * also be some encoded unique identifier for a federated identity account. @@ -59,6 +70,23 @@ public class Credential extends AutoSafeParcelable { return id; } + /** + * Returns the ID tokens that assert the identity of the user, if available. ID tokens provide + * a secure mechanism to verify that the user owns the identity asserted by the credential. + *

+ * This is useful for account hints, where the ID token can replace the need to separately + * verify that the user owns their claimed email address - with a valid ID token, it is not + * necessary to send an account activation link to the address, simplifying the account + * creation process for the user. + *

+ * A signed ID token is returned automatically for credential hints when the credential ID is a + * Google account that is authenticated on the device. This ID token can be sent along with + * your application's account creation operation, where the signature can be verified. + */ + public List getIdTokens() { + return tokens; + } + /** * Returns the display name of the credential, if available. Typically, the display name will * be the name of the user, or some other string which the user can easily recognize and @@ -68,22 +96,47 @@ public class Credential extends AutoSafeParcelable { return name; } + /** + * Returns the password used to sign in the user. + */ + public String getPassword() { + return password; + } + + /** + * Returns the URL to an image of the user, if available. + */ + public Uri getProfilePictureUri() { + return profilePictureUri; + } + + @PublicApi(exclude = true) + public String getAsString() { + if (TextUtils.isEmpty(accountType)) { + return id.toLowerCase(Locale.US) + "|"; + } else { + Uri uri = Uri.parse(accountType); + return id.toLowerCase(Locale.US) + "|" + (TextUtils.isEmpty(uri.getScheme()) ? "" : uri.getScheme().toLowerCase(Locale.US)) + "://" + + (TextUtils.isEmpty(uri.getHost()) ? "unknown" : uri.getHost().toLowerCase(Locale.US)) + ":" + uri.getPort(); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof Credential)) return false; + if (o == null || !(o instanceof Credential)) return false; Credential that = (Credential) o; - if (!Objects.equals(id, that.id)) return false; - if (!Objects.equals(name, that.name)) return false; - if (!Objects.equals(profilePictureUri, that.profilePictureUri)) + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + if (profilePictureUri != null ? !profilePictureUri.equals(that.profilePictureUri) : that.profilePictureUri != null) return false; - if (!Objects.equals(password, that.password)) + if (password != null ? !password.equals(that.password) : that.password != null) return false; - if (!Objects.equals(accountType, that.accountType)) + if (accountType != null ? !accountType.equals(that.accountType) : that.accountType != null) return false; - return Objects.equals(generatedPassword, that.generatedPassword); + return generatedPassword != null ? generatedPassword.equals(that.generatedPassword) : that.generatedPassword == null; } @@ -95,14 +148,18 @@ public class Credential extends AutoSafeParcelable { public static class Builder { private final String id; private String name; - private final Uri profilePictureUri; - private final String password; + private Uri profilePictureUri; + private String password; private String accountType; @PublicApi(exclude = true) public List tokens; @PublicApi(exclude = true) - private final String generatedPassword; + private String generatedPassword; + + public Builder(String id) { + this.id = id; + } /** * Copies the information stored in an existing credential, in order to allow that information to be modified. @@ -154,7 +211,24 @@ public class Credential extends AutoSafeParcelable { return this; } + /** + * Sets the password for the credential. Either the password or the account type must be + * set for a credential, but not both. + */ + public Builder setPassword(String password) { + this.password = password; + return this; + } + + /** + * Sets a profile picture associated with the credential, typically a picture the user has + * selected to represent the account. + */ + public Builder setProfilePictureUri(Uri profilePictureUri) { + this.profilePictureUri = profilePictureUri; + return this; + } } - public static final Creator CREATOR = new AutoCreator<>(Credential.class); + public static final Creator CREATOR = new AutoCreator(Credential.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialPickerConfig.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialPickerConfig.java index 41ca2191..4cb1bbc9 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialPickerConfig.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialPickerConfig.java @@ -8,20 +8,24 @@ package com.google.android.gms.auth.api.credentials; -import androidx.annotation.NonNull; - import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; @PublicApi public class CredentialPickerConfig extends AutoSafeParcelable { + @Field(1000) + private final int versionCode = 1; + @Field(1) - private final boolean showAddAccountButton; + private boolean showAddAccountButton; @Field(2) - private final boolean showCancelButton; + private boolean showCancelButton; @Field(3) - private final boolean forNewAccount; + private boolean forNewAccount; + + private CredentialPickerConfig() { + } public CredentialPickerConfig(boolean showAddAccountButton, boolean showCancelButton, boolean forNewAccount) { this.showAddAccountButton = showAddAccountButton; @@ -37,7 +41,14 @@ public class CredentialPickerConfig extends AutoSafeParcelable { return forNewAccount; } - @NonNull + public boolean shouldShowAddAccountButton() { + return showAddAccountButton; + } + + public boolean shouldShowCancelButton() { + return showCancelButton; + } + @Override public String toString() { return "CredentialPickerConfig{" + @@ -46,22 +57,39 @@ public class CredentialPickerConfig extends AutoSafeParcelable { '}'; } - public static class Builder { - private final boolean showAddAccountButton; - private final boolean showCancelButton; - private final boolean forNewAccount; - - public Builder(boolean showAddAccountButton, boolean showCancelButton, boolean forNewAccount) { - this.showAddAccountButton = showAddAccountButton; - this.showCancelButton = showCancelButton; - this.forNewAccount = forNewAccount; - } + public class Builder { + private boolean showAddAccountButton; + private boolean showCancelButton; + private boolean forNewAccount; public CredentialPickerConfig build() { return new CredentialPickerConfig(showAddAccountButton, showCancelButton, forNewAccount); } + /** + * Sets whether the hint request is for a new account sign-up flow. + */ + public Builder setForNewAccount(boolean forNewAccount) { + this.forNewAccount = forNewAccount; + return this; + } + + /** + * Sets whether the add account button should be shown in credential picker dialog. + */ + public Builder setShowAddAccountButton(boolean showAddAccountButton) { + this.showAddAccountButton = showAddAccountButton; + return this; + } + + /** + * Sets whether the cancel button should be shown in credential picker dialog. + */ + public Builder setShowCancelButton(boolean showCancelButton) { + this.showCancelButton = showCancelButton; + return this; + } } - public static final Creator CREATOR = new AutoCreator<>(CredentialPickerConfig.class); + public static final Creator CREATOR = new AutoCreator(CredentialPickerConfig.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialRequest.java index c4961d14..6255fbf9 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/CredentialRequest.java @@ -10,13 +10,43 @@ package com.google.android.gms.auth.api.credentials; import org.microg.safeparcel.AutoSafeParcelable; +/** + * Parameters for requesting a Credential, via Auth.CredentialsApi.request(). Instances can be + * created using CredentialRequest.Builder. + */ public class CredentialRequest extends AutoSafeParcelable { - @Field(1) - private final boolean passwordLoginSupported; + @Field(1000) + private final int versionCode = 1; - public CredentialRequest(boolean passwordLoginSupported) { + @Field(1) + private boolean passwordLoginSupported; + @Field(2) + private String[] accountTypes; + @Field(3) + private CredentialPickerConfig credentialPickerConfig; + @Field(4) + private CredentialPickerConfig credentialHintPickerConfig; + + private CredentialRequest() { } + + public CredentialRequest(boolean passwordLoginSupported, String[] accountTypes, CredentialPickerConfig credentialPickerConfig, CredentialPickerConfig credentialHintPickerConfig) { this.passwordLoginSupported = passwordLoginSupported; + this.accountTypes = accountTypes; + this.credentialPickerConfig = credentialPickerConfig; + this.credentialHintPickerConfig = credentialHintPickerConfig; + } + + public String[] getAccountTypes() { + return accountTypes; + } + + public CredentialPickerConfig getCredentialHintPickerConfig() { + return credentialHintPickerConfig; + } + + public CredentialPickerConfig getCredentialPickerConfig() { + return credentialPickerConfig; } /** @@ -34,12 +64,13 @@ public class CredentialRequest extends AutoSafeParcelable { public static final Creator CREATOR = new AutoCreator(CredentialRequest.class); public static class Builder { - - public Builder() { - } + private boolean passwordLoginSupported; + private String[] accountTypes; + private CredentialPickerConfig credentialPickerConfig; + private CredentialPickerConfig credentialHintPickerConfig; public void setAccountTypes(String... accountTypes) { - accountTypes.clone(); + this.accountTypes = accountTypes.clone(); } } } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/HintRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/HintRequest.java index 3f9336f4..1f1cae80 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/HintRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/HintRequest.java @@ -8,8 +8,6 @@ package com.google.android.gms.auth.api.credentials; -import androidx.annotation.NonNull; - import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; @@ -21,21 +19,26 @@ import java.util.Arrays; */ @PublicApi public class HintRequest extends AutoSafeParcelable { + @Field(1000) + private final int versionCode = 2; @Field(1) - private final CredentialPickerConfig hintPickerConfig; + private CredentialPickerConfig hintPickerConfig; @Field(2) - private final boolean emailAddressIdentifierSupported; + private boolean emailAddressIdentifierSupported; @Field(3) - private final boolean phoneNumberIdentifierSupported; + private boolean phoneNumberIdentifierSupported; @Field(4) - private final String[] accountTypes; + private String[] accountTypes; @Field(5) - private final boolean idTokenRequested; + private boolean idTokenRequested = true; @Field(6) - private final String serverClientId; + private String serverClientId; @Field(7) - private final String idTokenNonce; + private String idTokenNonce; + + private HintRequest() { + } public HintRequest(CredentialPickerConfig hintPickerConfig, boolean emailAddressIdentifierSupported, boolean phoneNumberIdentifierSupported, String[] accountTypes, boolean idTokenRequested, String serverClientId, String idTokenNonce) { this.hintPickerConfig = hintPickerConfig; @@ -47,9 +50,36 @@ public class HintRequest extends AutoSafeParcelable { this.idTokenNonce = idTokenNonce; } + public String[] getAccountTypes() { + return accountTypes; + } + + public CredentialPickerConfig getHintPickerConfig() { + return hintPickerConfig; + } + + public String getIdTokenNonce() { + return idTokenNonce; + } + + public String getServerClientId() { + return serverClientId; + } + + public boolean isEmailAddressIdentifierSupported() { + return emailAddressIdentifierSupported; + } + + public boolean isPhoneNumberIdentifierSupported() { + return phoneNumberIdentifierSupported; + } + + public boolean isIdTokenRequested() { + return idTokenRequested; + } + public static final Creator CREATOR = new AutoCreator<>(HintRequest.class); - @NonNull @Override public String toString() { return "HintRequest{" + @@ -64,29 +94,77 @@ public class HintRequest extends AutoSafeParcelable { } public static class Builder { - private final CredentialPickerConfig hintPickerConfig; - private final boolean emailAddressIdentifierSupported; - private final boolean phoneNumberIdentifierSupported; - private final String[] accountTypes; - private final String serverClientId; - private final String idTokenNonce; - - public Builder(CredentialPickerConfig hintPickerConfig, boolean emailAddressIdentifierSupported, boolean phoneNumberIdentifierSupported, String[] accountTypes, String serverClientId, String idTokenNonce) { - this.hintPickerConfig = hintPickerConfig; - this.emailAddressIdentifierSupported = emailAddressIdentifierSupported; - this.phoneNumberIdentifierSupported = phoneNumberIdentifierSupported; - this.accountTypes = accountTypes; - this.serverClientId = serverClientId; - this.idTokenNonce = idTokenNonce; - } + private CredentialPickerConfig hintPickerConfig; + private boolean emailAddressIdentifierSupported; + private boolean phoneNumberIdentifierSupported; + private String[] accountTypes; + private boolean idTokenRequested = true; + private String serverClientId; + private String idTokenNonce; /** * Builds a {@link HintRequest}. */ public HintRequest build() { - boolean idTokenRequested = true; return new HintRequest(hintPickerConfig, emailAddressIdentifierSupported, phoneNumberIdentifierSupported, accountTypes, idTokenRequested, serverClientId, idTokenNonce); } + /** + * Sets the account types (identity providers) that are accepted by this application. + * It is strongly recommended that the strings listed in {@link IdentityProviders} be used for the most common + * identity providers, and strings representing the login domain of the identity provider be used for any + * others which are not listed. + * + * @param accountTypes The list of account types (identity providers) supported by the app. + * typically in the form of the associated login domain for each identity provider. + */ + public void setAccountTypes(String... accountTypes) { + this.accountTypes = accountTypes.clone(); + } + + /** + * Enables returning {@link Credential} hints where the identifier is an email address, intended for use with a password chosen by the user. + */ + public void setEmailAddressIdentifierSupported(boolean emailAddressIdentifierSupported) { + this.emailAddressIdentifierSupported = emailAddressIdentifierSupported; + } + + /** + * Sets the configuration for the hint picker dialog. + */ + public void setHintPickerConfig(CredentialPickerConfig hintPickerConfig) { + this.hintPickerConfig = hintPickerConfig; + } + + /** + * Specify a nonce value that should be included in any generated ID token for this request. + */ + public void setIdTokenNonce(String idTokenNonce) { + this.idTokenNonce = idTokenNonce; + } + + /** + * Specify whether an ID token should be acquired for hints, if available for the selected credential identifier. + * This is enabled by default; disable this if your app does not use ID tokens as part of authentication to decrease latency in retrieving credentials and credential hints. + */ + public void setIdTokenRequested(boolean idTokenRequested) { + this.idTokenRequested = idTokenRequested; + } + + /** + * Enables returning {@link Credential} hints where the identifier is a phone number, intended for use with a password chosen by the user or SMS verification. + */ + public void setPhoneNumberIdentifierSupported(boolean phoneNumberIdentifierSupported) { + this.phoneNumberIdentifierSupported = phoneNumberIdentifierSupported; + } + + /** + * Specify the server client ID for the backend associated with this app. + * If a Google ID token can be generated for a retrieved credential or hint, and the specified server client ID is correctly configured to be associated with the app, then it will be used as the audience of the generated token. + * If a null value is specified, the default audience will be used for the generated ID token. + */ + public void setServerClientId(String serverClientId) { + this.serverClientId = serverClientId; + } } } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdToken.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdToken.java index 5d84423e..aa6d19d4 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdToken.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdToken.java @@ -14,11 +14,17 @@ import org.microg.safeparcel.AutoSafeParcelable; @PublicApi public class IdToken extends AutoSafeParcelable { + @Field(1000) + private final int versionCode = 1; + @Field(1) - private final String accountType; + private String accountType; @Field(2) - private final String id; + private String id; + + private IdToken() { + } public IdToken(String accountType, String id) { this.accountType = accountType; @@ -32,5 +38,12 @@ public class IdToken extends AutoSafeParcelable { return accountType; } - public static final Creator CREATOR = new AutoCreator<>(IdToken.class); + /** + * Returns the ID token, formatted according to the rules defined by the account type. + */ + public String getIdToken() { + return id; + } + + public static final Creator CREATOR = new AutoCreator(IdToken.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdentityProviders.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdentityProviders.java index 1d974993..8cd24eed 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdentityProviders.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/IdentityProviders.java @@ -8,8 +8,27 @@ package com.google.android.gms.auth.api.credentials; +import android.accounts.Account; + /** * Identity provider constants for use with {@link CredentialRequest.Builder#setAccountTypes(String...)} */ public final class IdentityProviders { + public static final String FACEBOOK = "//www.facebook.com"; + public static final String GOOGLE = "//accounts.google.com"; + public static final String LINKEDIN = "//www.linkedin.com"; + public static final String MICROSOFT = "//login.live.com"; + public static final String PAYPAL = "//www.paypal.com"; + public static final String TWITTER = "//twitter.com"; + public static final String YAHOO = "//login.yahoo.com"; + + /** + * Attempts to translate the account type in the provided account into the string that should be used in the credentials API. + * + * @param account an account on the device. + * @return The identity provider string for use with the Credentials API, or {@code null} if the account type is unknown. + */ + public static String getIdentityProviderForAccount(Account account) { + return null; + } } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/DeleteRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/DeleteRequest.java index e560ddc5..bd7ae6f2 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/DeleteRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/DeleteRequest.java @@ -19,5 +19,5 @@ package com.google.android.gms.auth.api.credentials.internal; import org.microg.safeparcel.AutoSafeParcelable; public class DeleteRequest extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(DeleteRequest.class); + public static final Creator CREATOR = new AutoCreator(DeleteRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/GeneratePasswordRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/GeneratePasswordRequest.java index 5fd6fcb7..3da7a72a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/GeneratePasswordRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/GeneratePasswordRequest.java @@ -19,5 +19,5 @@ package com.google.android.gms.auth.api.credentials.internal; import org.microg.safeparcel.AutoSafeParcelable; public class GeneratePasswordRequest extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(GeneratePasswordRequest.class); + public static final Creator CREATOR = new AutoCreator(GeneratePasswordRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/SaveRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/SaveRequest.java index 24a9d92f..5ec25998 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/SaveRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/credentials/internal/SaveRequest.java @@ -19,5 +19,5 @@ package com.google.android.gms.auth.api.credentials.internal; import org.microg.safeparcel.AutoSafeParcelable; public class SaveRequest extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(SaveRequest.class); + public static final Creator CREATOR = new AutoCreator(SaveRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java index dc1fbd39..acca03a4 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java @@ -5,16 +5,33 @@ package com.google.android.gms.auth.api.proxy; -import androidx.annotation.NonNull; +import android.os.Bundle; import org.microg.safeparcel.AutoSafeParcelable; public class ProxyRequest extends AutoSafeParcelable { + public static final int HTTP_METHOD_GET = 0; + public static final int HTTP_METHOD_POST = 1; + public static final int HTTP_METHOD_PUT = 2; + public static final int HTTP_METHOD_DELETE = 3; + public static final int HTTP_METHOD_HEAD = 4; + public static final int HTTP_METHOD_OPTIONS = 5; + public static final int HTTP_METHOD_TRACE = 6; + public static final int HTTP_METHOD_PATCH = 7; + @Field(1000) + private final int versionCode = 2; @Field(1) public String url; + @Field(2) + public int httpMethod; + @Field(3) + public long timeoutMillis; + @Field(4) + public byte[] body; + @Field(5) + public Bundle headers; - @NonNull @Override public String toString() { return url; diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java index 263d2e20..b18d21be 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java @@ -5,11 +5,26 @@ package com.google.android.gms.auth.api.proxy; +import android.app.PendingIntent; +import android.os.Bundle; + import org.microg.safeparcel.AutoSafeParcelable; public class ProxyResponse extends AutoSafeParcelable { + public static final int STATUS_CODE_NO_CONNECTION = -1; + + @Field(1000) + private final int versionCode = 1; @Field(1) public int gmsStatusCode; + @Field(2) + public PendingIntent recoveryAction; + @Field(3) + public int httpStatusCode; + @Field(4) + public Bundle headers; + @Field(5) + public byte[] body; public static final Creator CREATOR = new AutoCreator<>(ProxyResponse.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRecoveryGuidanceRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRecoveryGuidanceRequest.java index 1c267ed1..d5f1d054 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRecoveryGuidanceRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRecoveryGuidanceRequest.java @@ -24,6 +24,8 @@ import org.microg.safeparcel.SafeParceled; public class AccountRecoveryGuidanceRequest extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; @SafeParceled(2) @Deprecated public final String accountName; @@ -40,5 +42,5 @@ public class AccountRecoveryGuidanceRequest extends AutoSafeParcelable { this.account = account; } - public static final Creator CREATOR = new AutoCreator<>(AccountRecoveryGuidanceRequest.class); + public static final Creator CREATOR = new AutoCreator(AccountRecoveryGuidanceRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRemovalRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRemovalRequest.java index dc90a7d0..439825c2 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRemovalRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/AccountRemovalRequest.java @@ -24,6 +24,8 @@ import org.microg.safeparcel.SafeParceled; public class AccountRemovalRequest extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; @SafeParceled(2) @Deprecated public final String accountName; diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ConfirmCredentialsRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ConfirmCredentialsRequest.java index 9edad8b2..16142d6a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ConfirmCredentialsRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ConfirmCredentialsRequest.java @@ -16,9 +16,19 @@ package com.google.android.gms.auth.firstparty.dataservice; +import com.google.android.gms.auth.firstparty.shared.AccountCredentials; +import com.google.android.gms.auth.firstparty.shared.CaptchaSolution; + import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class ConfirmCredentialsRequest extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; + @SafeParceled(2) + public AccountCredentials accountCredentials; + @SafeParceled(3) + public CaptchaSolution captchaSolution; - public static final Creator CREATOR = new AutoCreator<>(ConfirmCredentialsRequest.class); + public static final Creator CREATOR = new AutoCreator(ConfirmCredentialsRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenRequest.java index 73062668..099e56f4 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenRequest.java @@ -25,6 +25,8 @@ import org.microg.safeparcel.SafeParceled; // TODO public class TokenRequest extends AutoSafeParcelable{ + @SafeParceled(1) + private final int versionCode = 4; @SafeParceled(3) public String accountName; @SafeParceled(4) @@ -38,5 +40,5 @@ public class TokenRequest extends AutoSafeParcelable{ return new Account(accountName, accountType); } - public static final Creator CREATOR = new AutoCreator<>(TokenRequest.class); + public static final Creator CREATOR = new AutoCreator(TokenRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenResponse.java new file mode 100644 index 00000000..999e9a96 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/dataservice/TokenResponse.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package com.google.android.gms.auth.firstparty.dataservice; + +public class TokenResponse { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/delegate/ConfirmCredentialsWorkflowRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/delegate/ConfirmCredentialsWorkflowRequest.java index b8e5ed63..d4308ef7 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/delegate/ConfirmCredentialsWorkflowRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/delegate/ConfirmCredentialsWorkflowRequest.java @@ -17,18 +17,27 @@ package com.google.android.gms.auth.firstparty.delegate; import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; import android.os.Bundle; +import com.google.android.gms.auth.firstparty.shared.AppDescription; + import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; public class ConfirmCredentialsWorkflowRequest extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 3; @SafeParceled(2) public String accountName; + @SafeParceled(3) + public AppDescription appDescription; @SafeParceled(4) public Bundle extras; @SafeParceled(5) public Account account; + @SafeParceled(6) + public AccountAuthenticatorResponse accountAuthenticatorResponse; - public static final Creator CREATOR = new AutoCreator<>(ConfirmCredentialsWorkflowRequest.class); + public static final Creator CREATOR = new AutoCreator(ConfirmCredentialsWorkflowRequest.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/proximity/data/Permit.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/proximity/data/Permit.java new file mode 100644 index 00000000..139bf29b --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/proximity/data/Permit.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package com.google.android.gms.auth.firstparty.proximity.data; + +public class Permit { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AccountCredentials.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AccountCredentials.java index 67e7c7db..33995a9a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AccountCredentials.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AccountCredentials.java @@ -23,6 +23,8 @@ import org.microg.safeparcel.SafeParceled; // TODO public class AccountCredentials extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 2; @SafeParceled(3) public String accountName; @SafeParceled(9) @@ -32,5 +34,5 @@ public class AccountCredentials extends AutoSafeParcelable { return new Account(accountName, accountType); } - public static final Creator CREATOR = new AutoCreator<>(AccountCredentials.class); + public static final Creator CREATOR = new AutoCreator(AccountCredentials.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AppDescription.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AppDescription.java index 1ccdf4f6..efa4b2bc 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AppDescription.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/AppDescription.java @@ -17,8 +17,19 @@ package com.google.android.gms.auth.firstparty.shared; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class AppDescription extends AutoSafeParcelable { + @SafeParceled(1) + private final int versionCode = 1; + @SafeParceled(2) + public int callingUid; + @SafeParceled(3) + public String sessiondId; + @SafeParceled(4) + public String sessiondSig; + @SafeParceled(5) + public String callingPkg; - public static final Creator CREATOR = new AutoCreator<>(AppDescription.class); + public static final Creator CREATOR = new AutoCreator(AppDescription.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/CaptchaSolution.java b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/CaptchaSolution.java index ce1d3f61..c0cb9326 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/CaptchaSolution.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/firstparty/shared/CaptchaSolution.java @@ -17,9 +17,13 @@ package com.google.android.gms.auth.firstparty.shared; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; // TODO public class CaptchaSolution extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(CaptchaSolution.class); + @SafeParceled(1) + private final int versionCode = 1; + + public static final Creator CREATOR = new AutoCreator(CaptchaSolution.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/common/server/FavaDiagnosticsEntity.java b/play-services-api/src/main/java/com/google/android/gms/common/server/FavaDiagnosticsEntity.java index d32e668b..6f10959a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/common/server/FavaDiagnosticsEntity.java +++ b/play-services-api/src/main/java/com/google/android/gms/common/server/FavaDiagnosticsEntity.java @@ -20,5 +20,5 @@ import org.microg.safeparcel.AutoSafeParcelable; public class FavaDiagnosticsEntity extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(FavaDiagnosticsEntity.class); + public static final Creator CREATOR = new AutoCreator(FavaDiagnosticsEntity.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/identity/accounts/api/AccountData.java b/play-services-api/src/main/java/com/google/android/gms/identity/accounts/api/AccountData.java new file mode 100644 index 00000000..d909cbf3 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/identity/accounts/api/AccountData.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package com.google.android.gms.identity.accounts.api; + +public class AccountData { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/CountrySpecification.java b/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/CountrySpecification.java new file mode 100644 index 00000000..b9770e36 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/CountrySpecification.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package com.google.android.gms.identity.intents.model; + +public class CountrySpecification { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/UserAddress.java b/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/UserAddress.java new file mode 100644 index 00000000..aa3959f2 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/identity/intents/model/UserAddress.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package com.google.android.gms.identity.intents.model; + +public class UserAddress { +} diff --git a/play-services-api/src/main/java/com/google/android/gms/people/internal/ParcelableLoadImageOptions.java b/play-services-api/src/main/java/com/google/android/gms/people/internal/ParcelableLoadImageOptions.java index 64322959..d0a7a742 100644 --- a/play-services-api/src/main/java/com/google/android/gms/people/internal/ParcelableLoadImageOptions.java +++ b/play-services-api/src/main/java/com/google/android/gms/people/internal/ParcelableLoadImageOptions.java @@ -17,8 +17,20 @@ package com.google.android.gms.people.internal; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class ParcelableLoadImageOptions extends AutoSafeParcelable { + @SafeParceled(1000) + private final int versionCode = 1; - public static final Creator CREATOR = new AutoCreator<>(ParcelableLoadImageOptions.class); + @SafeParceled(1) + public int imageSize; + + @SafeParceled(2) + public int avatarOptions; + + @SafeParceled(3) + public boolean useLargePictureForCp2Images; + + public static final Creator CREATOR = new AutoCreator(ParcelableLoadImageOptions.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/people/model/AccountMetadata.java b/play-services-api/src/main/java/com/google/android/gms/people/model/AccountMetadata.java index dfb64544..53038bbc 100644 --- a/play-services-api/src/main/java/com/google/android/gms/people/model/AccountMetadata.java +++ b/play-services-api/src/main/java/com/google/android/gms/people/model/AccountMetadata.java @@ -17,8 +17,24 @@ package com.google.android.gms.people.model; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class AccountMetadata extends AutoSafeParcelable { - public static Creator CREATOR = new AutoCreator<>(AccountMetadata.class); + @SafeParceled(1) + private final int versionCode = 2; + + @SafeParceled(2) + public boolean hasGooglePlus = true; + + @SafeParceled(3) + public boolean hasFeature2 = true; + + @SafeParceled(4) + public boolean hasFeature3 = true; + + @SafeParceled(5) + public boolean hasFeature4 = true; + + public static Creator CREATOR = new AutoCreator(AccountMetadata.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/people/model/AvatarReference.java b/play-services-api/src/main/java/com/google/android/gms/people/model/AvatarReference.java index 6e3da8e2..b03af386 100644 --- a/play-services-api/src/main/java/com/google/android/gms/people/model/AvatarReference.java +++ b/play-services-api/src/main/java/com/google/android/gms/people/model/AvatarReference.java @@ -17,11 +17,18 @@ package com.google.android.gms.people.model; import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; public class AvatarReference extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(AvatarReference.class); + @SafeParceled(1000) + private int versionCode; - public AvatarReference() { - } + @SafeParceled(1) + public int source; + + @SafeParceled(2) + public String location; + + public static final Creator CREATOR = new AutoCreator(AvatarReference.class); } diff --git a/play-services-base-api/build.gradle b/play-services-base-api/build.gradle index 59dc1be7..b806da4c 100644 --- a/play-services-base-api/build.gradle +++ b/play-services-base-api/build.gradle @@ -37,8 +37,4 @@ android { sourceCompatibility = 1.8 targetCompatibility = 1.8 } - namespace 'org.microg.gms.base.api' - buildFeatures { - aidl true - } } \ No newline at end of file diff --git a/play-services-base-api/src/main/AndroidManifest.xml b/play-services-base-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7c8eeef3 --- /dev/null +++ b/play-services-base-api/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java b/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java index abcdc6ca..4af2bcf1 100644 --- a/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java +++ b/play-services-base-api/src/main/java/com/google/android/gms/common/data/DataHolder.java @@ -17,13 +17,16 @@ package com.google.android.gms.common.data; import android.annotation.SuppressLint; +import android.content.ContentValues; +import android.database.AbstractWindowedCursor; +import android.database.CharArrayBuffer; import android.database.Cursor; import android.database.CursorWindow; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Parcel; -import androidx.annotation.NonNull; - import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; @@ -42,6 +45,9 @@ import java.util.Map; */ @PublicApi(until = "1") public class DataHolder extends AutoSafeParcelable implements Closeable { + @SafeParceled(1000) + private final int versionCode = 1; + @SafeParceled(1) private final String[] columns; @@ -63,6 +69,13 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { protected static final int FIELD_TYPE_STRING = 3; protected static final int FIELD_TYPE_BLOB = 4; + private DataHolder() { + columns = null; + windows = null; + statusCode = 0; + metadata = null; + } + /** * Creates a data holder with the specified data. * @@ -79,6 +92,17 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { validateContents(); } + /** + * Creates a data holder wrapping the provided cursor, with provided status code and metadata. + * + * @param cursor The cursor containing the data. + * @param statusCode The status code of this {@link DataHolder}. + * @param metadata The metadata associated with this {@link DataHolder} (may be null). + */ + public DataHolder(AbstractWindowedCursor cursor, int statusCode, Bundle metadata) { + this(cursor.getColumnNames(), createCursorWindows(cursor), statusCode, metadata); + } + /** * Creates a data holder wrapping the provided cursor, with provided status code and metadata. * @@ -94,14 +118,23 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { * Get a {@link DataHolder.Builder} to create a new {@link DataHolder} manually. * * @param columns The array of column names that the object supports. + * @param uniqueColumn The non-null column name that must contain unique values. New rows added to the builder with the same value in this column will replace any older rows. + * @return {@link DataHolder.Builder} object to work with. + */ + public static Builder builder(String[] columns, String uniqueColumn) { + return new Builder(columns, uniqueColumn); + } + + /** + * Get a {@link DataHolder.Builder} to create a new {@link DataHolder} manually. + * + * @param columns The array of column names that the object supports. * @return {@link DataHolder.Builder} object to work with. */ public static Builder builder(String[] columns) { - return new Builder(columns); + return builder(columns, null); } -<<<<<<< HEAD -======= /** * @param statusCode The status code of this {@link DataHolder}. * @param metadata The metadata associated with this {@link DataHolder} (may be null). @@ -119,7 +152,6 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return empty(statusCode, null); } ->>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) @SuppressLint({"NewApi", "ObsoleteSdkInt"}) static int getCursorType(Cursor cursor, int i) { return cursor.getType(i); @@ -140,6 +172,20 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { } } + /** + * Copies the String content in the given column at the provided position into a {@link CharArrayBuffer}. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @param dataOut The {@link CharArrayBuffer} to copy into. + */ + public void copyToBuffer(String column, int row, int windowIndex, CharArrayBuffer dataOut) { + throw new RuntimeException("Not yet available"); + } + @SuppressWarnings("deprecation") private static CursorWindow[] createCursorWindows(Builder builder) { if (builder.columns.length == 0) return new CursorWindow[0]; @@ -232,6 +278,34 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return windows.toArray(new CursorWindow[0]); } + /** + * Retrieves the boolean value for a given column at the provided position. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The boolean value in that column. + */ + public boolean getBoolean(String column, int row, int windowIndex) { + return windows[windowIndex].getLong(row, columnIndices.get(column)) == 1; + } + + /** + * Retrieves the byte array value for a given column at the provided position. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The byte array value in that column. + */ + public byte[] getByteArray(String column, int row, int windowIndex) { + return windows[windowIndex].getBlob(row, columnIndices.get(column)); + } + /** * Gets the number of rows in the data holder. * @@ -247,6 +321,38 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return c; } + /** + * Retrieves the integer value for a given column at the provided position. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The integer value in that column. + */ + public int getInteger(String column, int row, int windowIndex) { + return windows[windowIndex].getInt(row, columnIndices.get(column)); + } + + /** + * Retrieves the long value for a given column at the provided position. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The long value in that column. + */ + public long getLong(String column, int row, int windowIndex) { + return windows[windowIndex].getLong(row, columnIndices.get(column)); + } + + public int getStatusCode() { + return statusCode; + } + /** * Retrieves the string value for a given column at the provided position. * This will throw an {@link IllegalArgumentException} if the column does not exist, the @@ -261,7 +367,42 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return windows[windowIndex].getString(row, columnIndices.get(column)); } - @NonNull + /** + * Returns whether the given column at the provided position contains null. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return Whether the column value is null at this position. + */ + public boolean isNull(String column, int row, int windowIndex) { + return windows[windowIndex].isNull(row, columnIndices.get(column)); + } + + public boolean isClosed() { + synchronized (this) { + return closed; + } + } + + /** + * Retrieves the column data at the provided position as a URI if possible, checking for null values. + * This will throw an {@link IllegalArgumentException} if the column does not exist, the + * position is invalid, or the data holder has been closed. + * + * @param column The column to retrieve. + * @param row The row to retrieve the data from. + * @param windowIndex Index of the cursor window to extract the data from. + * @return The column data as a URI, or null if not present. + */ + public Uri parseUri(String column, int row, int windowIndex) { + String string = getString(column, row, windowIndex); + if (string != null) return Uri.parse(string); + return null; + } + @Override public String toString() { return "DataHolder{" + @@ -287,10 +428,14 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { public static class Builder { private final String[] columns; private final ArrayList> rows; + private final String uniqueColumn; + private final Map uniqueIndizes; - private Builder(String[] columns) { + private Builder(String[] columns, String uniqueColumn) { this.columns = columns; this.rows = new ArrayList<>(); + this.uniqueColumn = uniqueColumn; + this.uniqueIndizes = new HashMap<>(); } /** @@ -321,6 +466,58 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return rows.size(); } + /** + * Sort the rows in this builder based on the standard data type comparisons for the value in the provided column. + * Calling this multiple times with the same column will not change the sort order of the builder. + * Note that any data which is added after this call will not be sorted. + * + * @param sortColumn The column to sort the rows in this builder by. + * @return {@link DataHolder.Builder} to continue construction. + */ + public Builder sort(String sortColumn) { + throw new RuntimeException("Not yet implemented"); + } + + /** + * Add a new row of data to the {@link DataHolder} this {@link DataHolder.Builder} will create. Note that the data must contain an entry for all columns + *

+ * Currently the only supported value types that are supported are String, Long, and Boolean (Integer is also accepted and will be stored as a Long). + * + * @param values {@link ContentValues} containing row data. + * @return {@link DataHolder.Builder} to continue construction. + */ + public Builder withRow(ContentValues values) { + HashMap row = new HashMap<>(); + for (Map.Entry entry : values.valueSet()) { + row.put(entry.getKey(), entry.getValue()); + } + return withRow(row); + } + + /** + * Add a new row of data to the {@link DataHolder} this {@link DataHolder.Builder} will create. Note that the data must contain an entry for all columns + *

+ * Currently the only supported value types that are supported are String, Long, and Boolean (Integer is also accepted and will be stored as a Long). + * + * @param row Map containing row data. + * @return {@link DataHolder.Builder} to continue construction. + */ + public Builder withRow(HashMap row) { + if (uniqueColumn != null) { + Object val = row.get(uniqueColumn); + if (val != null) { + Integer old = uniqueIndizes.get(val); + if (old != null) { + rows.set(old, row); + return this; + } else { + uniqueIndizes.put(val, rows.size()); + } + } + } + rows.add(row); + return this; + } } public static final Creator CREATOR = new AutoCreator(DataHolder.class) { diff --git a/play-services-base-api/src/main/java/com/google/android/gms/common/data/Freezable.java b/play-services-base-api/src/main/java/com/google/android/gms/common/data/Freezable.java new file mode 100644 index 00000000..51f4847e --- /dev/null +++ b/play-services-base-api/src/main/java/com/google/android/gms/common/data/Freezable.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package com.google.android.gms.common.data; + +public interface Freezable { + /** + * Freeze a volatile representation into an immutable representation. Objects returned from + * this call are safe to cache. + *

+ * Note that the output of {@link #freeze} may not be identical to the parent object, but + * should be equal. + * + * @return A concrete implementation of the data object. + */ + T freeze(); + + /** + * Check to see if this object is valid for use. If the object is still volatile, this method + * will indicate whether or not the object can be safely used. + * The output of a call to {@link #freeze()} will always be valid. + * + * @return whether or not the object is valid for use. + */ + boolean isDataValid(); +} diff --git a/play-services-base-api/src/main/java/com/google/android/gms/common/images/WebImage.java b/play-services-base-api/src/main/java/com/google/android/gms/common/images/WebImage.java index 4b5e4ad7..fee75495 100644 --- a/play-services-base-api/src/main/java/com/google/android/gms/common/images/WebImage.java +++ b/play-services-base-api/src/main/java/com/google/android/gms/common/images/WebImage.java @@ -18,8 +18,6 @@ package com.google.android.gms.common.images; import android.net.Uri; -import androidx.annotation.NonNull; - import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; @@ -60,9 +58,7 @@ public class WebImage extends AutoSafeParcelable { return height; } - @NonNull public String toString() { - assert uri != null; - return String.format(Locale.getDefault(), "Image %dx%d %s", width, height, uri); + return String.format(Locale.getDefault(), "Image %dx%d %s", width, height, uri.toString()); } } diff --git a/play-services-base-core/build.gradle b/play-services-base-core/build.gradle index 34288f21..ad962700 100644 --- a/play-services-base-core/build.gradle +++ b/play-services-base-core/build.gradle @@ -21,6 +21,7 @@ android { buildToolsVersion androidBuildVersionTools defaultConfig { + versionName version minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk @@ -32,8 +33,7 @@ android { } compileOptions { - sourceCompatibility = 17 - targetCompatibility = 17 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 } - namespace 'org.microg.gms.base.core' } \ No newline at end of file diff --git a/play-services-base-core/src/main/AndroidManifest.xml b/play-services-base-core/src/main/AndroidManifest.xml index f260e226..39554ca3 100644 --- a/play-services-base-core/src/main/AndroidManifest.xml +++ b/play-services-base-core/src/main/AndroidManifest.xml @@ -3,13 +3,10 @@ ~ SPDX-FileCopyrightText: 2020, microG Project Team ~ SPDX-License-Identifier: Apache-2.0 --> -<<<<<<< HEAD - -======= ->>>>>>> 0f8fb823 (Expose device profile as content https://github.com/microg/GmsCore/commit/9d5a99710f0cbe9055a43df371eaae77610583f3) + >>>>>> 25329625 (Set basePackageName) getService(callback, request); } + private Scope[] scopesFromStringArray(String[] arr) { + Scope[] scopes = new Scope[arr.length]; + for (int i = 0; i < arr.length; i++) { + scopes[i] = new Scope(arr[i]); + } + return scopes; + } + @Override public void getService(IGmsCallbacks callback, GetServiceRequest request) throws RemoteException { GmsService gmsService = GmsService.byServiceId(request.serviceId); @@ -94,7 +104,7 @@ public abstract class AbstractGmsServiceBroker extends IGmsServiceBroker.Stub { public abstract void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException; @Override - public void validateAccount(IGmsCallbacks callback, ValidateAccountRequest request) { + public void validateAccount(IGmsCallbacks callback, ValidateAccountRequest request) throws RemoteException { throw new IllegalArgumentException("ValidateAccountRequest not supported"); } diff --git a/play-services-base-core/src/main/java/org/microg/gms/BaseService.java b/play-services-base-core/src/main/java/org/microg/gms/BaseService.java index 38545ff0..3941e508 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/BaseService.java +++ b/play-services-base-core/src/main/java/org/microg/gms/BaseService.java @@ -21,7 +21,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import androidx.annotation.NonNull; import androidx.lifecycle.LifecycleService; import com.google.android.gms.common.internal.GetServiceRequest; @@ -59,7 +58,7 @@ public abstract class BaseService extends LifecycleService { } @Override - public IBinder onBind(@NonNull Intent intent) { + public IBinder onBind(Intent intent) { super.onBind(intent); Log.d(TAG, "onBind: " + intent); return broker.asBinder(); diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java b/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java index 7a57f39e..55bffb3c 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/DeviceConfiguration.java @@ -112,10 +112,6 @@ public class DeviceConfiguration { @SuppressWarnings({"InlinedApi"}) private static List getNativePlatforms() { -<<<<<<< HEAD - List nativePlatforms; -======= ->>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) return Arrays.asList(Build.SUPPORTED_ABIS); } diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java b/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java index 0cf5360a..417ad6f7 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/ForegroundServiceContext.java @@ -1,10 +1,7 @@ package org.microg.gms.common; -<<<<<<< HEAD -======= import static android.os.Build.VERSION.SDK_INT; ->>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationChannel; @@ -89,11 +86,7 @@ public class ForegroundServiceContext extends ContextWrapper { } @SuppressLint("BatteryLife") -<<<<<<< HEAD - private static Notification buildForegroundNotification(Context context, String serviceName) { -======= private static Notification buildForegroundNotification(Context context) { ->>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) Intent notificationIntent = new Intent(); if (SDK_INT >= 31) { notificationIntent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java index 18220e82..03fd8b93 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java @@ -16,13 +16,10 @@ package org.microg.gms.common; -<<<<<<< HEAD -======= import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; import static org.microg.gms.common.Constants.GMS_PACKAGE_SIGNATURE_SHA1; import android.annotation.SuppressLint; ->>>>>>> 25329625 (Set basePackageName) import android.app.ActivityManager; import android.app.Application; import android.app.PendingIntent; @@ -46,17 +43,11 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import static android.os.Build.VERSION.SDK_INT; -import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; -import static org.microg.gms.common.Constants.GMS_PACKAGE_SIGNATURE_SHA1; -import static org.microg.gms.common.Constants.GMS_SECONDARY_PACKAGE_SIGNATURE_SHA1; - public class PackageUtils { private static final String GOOGLE_PLATFORM_KEY = GMS_PACKAGE_SIGNATURE_SHA1; - private static final String GOOGLE_PLATFORM_KEY_2 = GMS_SECONDARY_PACKAGE_SIGNATURE_SHA1; - private static final String GOOGLE_APP_KEY = "24bb24c05e47e0aefa68a58a766179d9b613a600"; - private static final String[] GOOGLE_PRIMARY_KEYS = {GOOGLE_PLATFORM_KEY, GOOGLE_PLATFORM_KEY_2, GOOGLE_APP_KEY}; + private static final String GOOGLE_LEGACY_KEY = "58e1c4133f7441ec3d2c270270a14802da47ba0e"; // Seems to be no longer used. + private static final String[] GOOGLE_PRIMARY_KEYS = {GOOGLE_PLATFORM_KEY, "24bb24c05e47e0aefa68a58a766179d9b613a600", "afb0fed5eeaebdd86f56a97742f4b6b33ef59875", "61226bdb57cc32c8a2a9ef71f7bc9548e95dcc0b", "3a82b5ee26bc46bf68113d920e610cd090198d4a"}; public static final Map KNOWN_GOOGLE_PACKAGES; @@ -64,7 +55,6 @@ public class PackageUtils { KNOWN_GOOGLE_PACKAGES = new HashMap<>(); KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.youtube", "24bb24c05e47e0aefa68a58a766179d9b613a600"); KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.youtube.music", "afb0fed5eeaebdd86f56a97742f4b6b33ef59875"); - KNOWN_GOOGLE_PACKAGES.put("com.google.android.youtube", "24bb24c05e47e0aefa68a58a766179d9b613a600"); } public static boolean isGooglePackage(Context context, String packageName) { @@ -126,7 +116,7 @@ public class PackageUtils { if (packageName.equals("com.google.android.youtube.tv") || packageName.contains("youtube.tv")) { return "61226bdb57cc32c8a2a9ef71f7bc9548e95dcc0b"; } - if (packageName.equals("com.google.android.youtube") || packageName.contains("youtube")) { + if (packageName.equals("com.google.android.apps.photos") || packageName.contains("apps.photos") || packageName.equals("com.google.android.youtube") || packageName.contains("youtube")) { return "24bb24c05e47e0aefa68a58a766179d9b613a600"; } final PackageInfo info; @@ -298,10 +288,7 @@ public class PackageUtils { @SuppressLint("PrivateApi") Class activityThread = Class.forName("android.app.ActivityThread"); String methodName = "currentProcessName"; -<<<<<<< HEAD -======= @SuppressLint("DiscouragedPrivateApi") ->>>>>>> 25329625 (Set basePackageName) Method getProcessName = activityThread.getDeclaredMethod(methodName); return (String) getProcessName.invoke(null); } catch (Exception e) { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt index 0f18d221..7aec1077 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/common/PackageSpoofUtils.kt @@ -141,6 +141,7 @@ internal object PackageSpoofUtils { * @param packageName name of the package to get metadata of * @return package metadata bundle */ + @Suppress("DEPRECATION") private fun getPackageMetadata(packageManager: PackageManager, packageName: String): Bundle? { return try { // PackageManager.getPackageInfo() has been deprecated in targetSdkVersion 30+ 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 564d8708..725e0d6a 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 @@ -5,6 +5,8 @@ package org.microg.gms.profile +import android.annotation.TargetApi + object Build { @JvmField var BOARD: String? = null diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt index bad9447a..76286eab 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt @@ -32,11 +32,7 @@ object ProfileManager { private fun getSystemProfileFile(): File = File("/system/etc/microg_device_profile.xml") private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".lowercase(Locale.US), null, null) - fun getConfiguredProfile(context: Context): String = Profile.getContentUri(context)?.let { - SettingsContract.getSettings(context, - it, arrayOf(Profile.PROFILE)) { it.getString(0) } - } - ?: PROFILE_AUTO + fun getConfiguredProfile(context: Context): String = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } ?: PROFILE_AUTO fun getAutoProfile(context: Context): String { if (hasProfile(context, PROFILE_SYSTEM) && isAutoProfile(context, PROFILE_SYSTEM)) return PROFILE_SYSTEM @@ -136,14 +132,8 @@ object ProfileManager { } private fun getProfile(context: Context) = getConfiguredProfile(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } - private fun getSerialFromSettings(context: Context): String? = - Profile.getContentUri(context)?.let { - SettingsContract.getSettings(context, - it, arrayOf(Profile.SERIAL)) { it.getString(0) } - } - private fun saveSerial(context: Context, serial: String) = - Profile.getContentUri(context) - ?.let { SettingsContract.setSettings(context, it) { put(Profile.SERIAL, serial) } } + private fun getSerialFromSettings(context: Context): String? = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.SERIAL)) { it.getString(0) } + private fun saveSerial(context: Context, serial: String) = SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.SERIAL, serial) } private fun randomSerial(template: String, prefixLength: Int = (template.length / 2).coerceAtMost(6)): String { val serial = StringBuilder() @@ -341,11 +331,9 @@ object ProfileManager { val changed = getProfile(context) != profile val newProfile = profile ?: PROFILE_AUTO val newSerial = if (changed) getSerial(context, newProfile, true) else getSerial(context) - Profile.getContentUri(context)?.let { - SettingsContract.setSettings(context, it) { - put(Profile.PROFILE, newProfile) - if (changed) put(Profile.SERIAL, newSerial) - } + SettingsContract.setSettings(context, Profile.getContentUri(context)) { + put(Profile.PROFILE, newProfile) + if (changed) put(Profile.SERIAL, newSerial) } if (changed && activeProfile != null) applyProfile(context, newProfile, newSerial) } diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index bf0d7042..11d52bf9 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -12,7 +12,8 @@ object SettingsContract { object CheckIn { private const val id = "check-in" - fun getContentUri(context: Context): Uri? = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" const val ENABLED = "checkin_enable_service" const val ANDROID_ID = "androidId" @@ -43,7 +44,8 @@ object SettingsContract { object Gcm { private const val id = "gcm" - fun getContentUri(context: Context): Uri? = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" const val FULL_LOG = "gcm_full_log" const val LAST_PERSISTENT_ID = "gcm_last_persistent_id" @@ -74,7 +76,8 @@ object SettingsContract { object Auth { private const val id = "auth" - fun getContentUri(context: Context): Uri? = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" const val TRUST_GOOGLE = "auth_manager_trust_google" const val VISIBLE = "auth_manager_visible" @@ -89,7 +92,8 @@ object SettingsContract { object Profile { private const val id = "profile" - fun getContentUri(context: Context): Uri? = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" const val PROFILE = "device_profile" const val SERIAL = "device_profile_serial" diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index 9a5a0e31..39dc00e1 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -6,7 +6,6 @@ package org.microg.gms.settings -import android.annotation.SuppressLint import android.content.ContentProvider import android.content.ContentValues import android.content.Context @@ -30,7 +29,6 @@ private const val SETTINGS_PREFIX = "org.microg.gms.settings." * All settings access should go through this [ContentProvider], * because it provides safe access from different processes which normal [SharedPreferences] don't. */ -@SuppressLint("DiscouragedPrivateApi") class SettingsProvider : ContentProvider() { private val preferences: SharedPreferences by lazy { @@ -170,15 +168,9 @@ class SettingsProvider : ContentProvider() { Gcm.NETWORK_ROAMING -> Integer.parseInt(preferences.getString(key, "0") ?: "0") Gcm.NETWORK_OTHER -> Integer.parseInt(preferences.getString(key, "0") ?: "0") -<<<<<<< HEAD - Gcm.LEARNT_MOBILE -> preferences.getInt(key, INTERVAL) - Gcm.LEARNT_WIFI -> preferences.getInt(key, INTERVAL) - Gcm.LEARNT_OTHER -> preferences.getInt(key, INTERVAL) -======= Gcm.LEARNT_MOBILE -> preferences.getInt(key, 300000) Gcm.LEARNT_WIFI -> preferences.getInt(key, 300000) Gcm.LEARNT_OTHER -> preferences.getInt(key, 300000) ->>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) else -> throw IllegalArgumentException("Unknown key: $key") } @@ -283,15 +275,13 @@ class SettingsProvider : ContentProvider() { return listOf(preferences, systemDefaultPreferences, metaDataPreferences).getBooleanAsInt(key, def) } -<<<<<<< HEAD - private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences).getString(key, def) -======= private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getString(key, def) private fun getSettingsInt(key: String, def: Int): Int = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getInt(key, def) private fun getSettingsLong(key: String, def: Long): Long = listOf(preferences, systemDefaultPreferences, metaDataPreferences).getLong(key, def) ->>>>>>> d2450feb (Base: Allow default settings to be provided in meta-data https://github.com/microg/GmsCore/commit/2c80de55d132a91c25036880517c5605c6753f71) private fun List.getString(key: String, def: String?): String? = foldRight(def) { preferences, defValue -> preferences?.getString(key, defValue) ?: defValue } + private fun List.getInt(key: String, def: Int): Int = foldRight(def) { preferences, defValue -> preferences?.getInt(key, defValue) ?: defValue } + private fun List.getLong(key: String, def: Long): Long = foldRight(def) { preferences, defValue -> preferences?.getLong(key, defValue) ?: defValue } private fun List.getBoolean(key: String, def: Boolean): Boolean = foldRight(def) { preferences, defValue -> preferences?.getBoolean(key, defValue) ?: defValue } private fun List.getBooleanAsInt(key: String, def: Boolean): Int = if (getBoolean(key, def)) 1 else 0 } diff --git a/play-services-base-core/src/main/res/values-ja/strings.xml b/play-services-base-core/src/main/res/values-ja/strings.xml index 97f279c1..a603746e 100644 --- a/play-services-base-core/src/main/res/values-ja/strings.xml +++ b/play-services-base-core/src/main/res/values-ja/strings.xml @@ -4,8 +4,8 @@ ~ SPDX-License-Identifier: Apache-2.0 --> - 電池の最適化 の警告 + バッテリー節約の警告 通知を展開する - Vanced microGをバックグラウンドで保持 - ここをタップして、Vanced microGの[電池の最適化]を無効にし、デバイスを再起動して通知を削除してください。 + microGをバックグラウンドで保持 + こちらをタップして、microGのバッテリー最適化を無効にし、デバイスを再起動して通知を削除する diff --git a/play-services-base/build.gradle b/play-services-base/build.gradle index 7c7057d4..8857d222 100644 --- a/play-services-base/build.gradle +++ b/play-services-base/build.gradle @@ -26,10 +26,9 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 } - namespace 'org.microg.gms.base' } dependencies { diff --git a/play-services-base/src/main/AndroidManifest.xml b/play-services-base/src/main/AndroidManifest.xml index c31f1c05..3ab02d97 100644 --- a/play-services-base/src/main/AndroidManifest.xml +++ b/play-services-base/src/main/AndroidManifest.xml @@ -15,7 +15,8 @@ ~ limitations under the License. --> - + @@ -241,5 +299,4 @@ public class GoogleApiAvailability { // TODO } } ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesClient.java b/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesClient.java index 0941e410..a5642fdd 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesClient.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesClient.java @@ -26,6 +26,20 @@ public interface GooglePlayServicesClient { boolean isConnected(); + boolean isConnecting(); + + void registerConnectionCallbacks(ConnectionCallbacks listener); + + boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener); + + void unregisterConnectionCallbacks(ConnectionCallbacks listener); + + void registerConnectionFailedListener(OnConnectionFailedListener listener); + + boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener); + + void unregisterConnectionFailedListener(OnConnectionFailedListener listener); + @Deprecated interface OnConnectionFailedListener { diff --git a/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java b/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java index d5399280..4850b816 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java @@ -16,8 +16,14 @@ package com.google.android.gms.common; +import android.app.Activity; import android.app.Dialog; +import android.app.Fragment; import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.content.res.Resources; import android.util.Log; import org.microg.gms.common.Constants; @@ -34,6 +40,8 @@ import org.microg.gms.common.PublicApi; public class GooglePlayServicesUtil { private static final String TAG = "GooglePlayServicesUtil"; + public static final String GMS_ERROR_DIALOG = "GooglePlayServicesErrorDialog"; + /** * Package name for Google Play services. */ @@ -46,16 +54,45 @@ public class GooglePlayServicesUtil { @Deprecated public static final int GOOGLE_PLAY_SERVICES_VERSION_CODE = Constants.GMS_VERSION_CODE; + /** + * Package name for Google Play Store. + */ + public static final String GOOGLE_PLAY_STORE_PACKAGE = "com.android.vending"; + /** * Returns a dialog to address the provided errorCode. The returned dialog displays a localized * message about the error and upon user confirmation (by tapping on dialog) will direct them * to the Play Store if Google Play services is out of date or missing, or to system settings * if Google Play services is disabled on the device. * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent activity for creating the dialog, also used for identifying + * language to display dialog in. + * @param requestCode The requestCode given when calling startActivityForResult. */ @Deprecated - public static Dialog getErrorDialog() { - return GoogleApiAvailability.getInstance().getErrorDialog(); + public static Dialog getErrorDialog(int errorCode, Activity activity, int requestCode) { + return getErrorDialog(errorCode, activity, requestCode, null); + } + + /** + * Returns a dialog to address the provided errorCode. The returned dialog displays a localized + * message about the error and upon user confirmation (by tapping on dialog) will direct them + * to the Play Store if Google Play services is out of date or missing, or to system settings + * if Google Play services is disabled on the device. + * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent activity for creating the dialog, also used for identifying + * language to display dialog in. + * @param requestCode The requestCode given when calling startActivityForResult. + * @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog + * is canceled. + */ + @Deprecated + public static Dialog getErrorDialog(int errorCode, Activity activity, int requestCode, DialogInterface.OnCancelListener cancelListener) { + return GoogleApiAvailability.getInstance().getErrorDialog(activity, errorCode, requestCode, cancelListener); } /** @@ -63,17 +100,22 @@ public class GooglePlayServicesUtil { * following places to either the Play Store if Google Play services is out of date or missing, * or system settings if Google Play services is disabled on the device. * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent context for creating the PendingIntent. + * @param requestCode The requestCode given when calling startActivityForResult. */ @Deprecated - public static PendingIntent getErrorPendingIntent() { + public static PendingIntent getErrorPendingIntent(int errorCode, Activity activity, + int requestCode) { return null; // TODO } /** - * Returns a human-readable string of the error code returned from {@link #isGooglePlayServicesAvailable()}. + * Returns a human-readable string of the error code returned from {@link #isGooglePlayServicesAvailable(Context)}. */ @Deprecated - public static String getErrorString() { + public static String getErrorString(int errorCode) { return null; // TODO } @@ -82,7 +124,27 @@ public class GooglePlayServicesUtil { * application, or null if Google Play services is not available on this device. */ @Deprecated - public static String getOpenSourceSoftwareLicenseInfo() { + public static String getOpenSourceSoftwareLicenseInfo(Context context) { + return null; // TODO + } + + /** + * This gets the Context object of the Buddy APK. This loads the Buddy APK code from the Buddy + * APK into memory. This returned context can be used to create classes and obtain resources + * defined in the Buddy APK. + * + * @return The Context object of the Buddy APK or null if the Buddy APK is not installed on the device. + */ + public static Context getRemoteContext(Context context) { + return null; // TODO + } + + /** + * This gets the Resources object of the Buddy APK. + * + * @return The Resources object of the Buddy APK or null if the Buddy APK is not installed on the device. + */ + public static Resources getRemoteResources(Context context) { return null; // TODO } @@ -95,41 +157,79 @@ public class GooglePlayServicesUtil { * SERVICE_DISABLED, SERVICE_INVALID */ @Deprecated - public static int isGooglePlayServicesAvailable() { + public static int isGooglePlayServicesAvailable(Context context) { Log.d(TAG, "As we can't know right now if the later desired feature is available, " + "we just pretend it to be."); return ConnectionResult.SUCCESS; } @Deprecated - public static boolean isGoogleSignedUid() { + public static boolean isGoogleSignedUid(PackageManager packageManager, int uid) { return false; // TODO } /** * Determines whether an error is user-recoverable. If true, proceed by calling - * {@link #getErrorDialog()} and showing the dialog. + * {@link #getErrorDialog(int, Activity, int)} and showing the dialog. * - * @return true if the error is recoverable with {@link #getErrorDialog()} + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)}, or + * returned to your application via {@link com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} + * @return true if the error is recoverable with {@link #getErrorDialog(int, Activity, int)} */ @Deprecated - public static boolean isUserRecoverableError() { - return false; // TODO - } - - @Deprecated - public static boolean showErrorDialogFragment() { + public static boolean isUserRecoverableError(int errorCode) { return false; // TODO } /** - * Displays a notification relevant to the provided error code. This method is similar to - * {@link #getErrorDialog()}, but is provided for background - * tasks that cannot or shouldn't display dialogs. + * Display a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}. * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent activity for creating the dialog, also used for identifying + * language to display dialog in. + * @param requestCode The requestCode given when calling startActivityForResult. + * @return true if the dialog is shown, false otherwise + * @throws RuntimeException if API level is below 11 and activity is not a {@link android.support.v4.app.FragmentActivity}. */ @Deprecated - public static void showErrorNotification() { + public static boolean showErrorDialogFragment(int errorCode, Activity activity, int requestCode) { + return showErrorDialogFragment(errorCode, activity, requestCode, null); + } + + @Deprecated + public static boolean showErrorDialogFragment(int errorCode, Activity activity, Fragment fragment, int requestCode, DialogInterface.OnCancelListener cancelListener) { + return false; // TODO + } + + /** + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param activity parent activity for creating the dialog, also used for identifying + * language to display dialog in. + * @param requestCode The requestCode given when calling startActivityForResult. + * @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog + * is canceled. + * @return true if the dialog is shown, false otherwise. + * @throws RuntimeException if API level is below 11 and activity is not a {@link android.support.v4.app.FragmentActivity}. + */ + @Deprecated + public static boolean showErrorDialogFragment(int errorCode, Activity activity, int requestCode, DialogInterface.OnCancelListener cancelListener) { + return showErrorDialogFragment(errorCode, activity, null, requestCode, cancelListener); + } + + /** + * Displays a notification relevant to the provided error code. This method is similar to + * {@link #getErrorDialog(int, android.app.Activity, int)}, but is provided for background + * tasks that cannot or shouldn't display dialogs. + * + * @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call. + * If errorCode is {@link ConnectionResult#SUCCESS} then null is returned. + * @param context used for identifying language to display dialog in as well as accessing the + * {@link android.app.NotificationManager}. + */ + @Deprecated + public static void showErrorNotification(int errorCode, Context context) { // TODO } } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java b/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java index a6942637..fd91a375 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/Api.java @@ -16,14 +16,11 @@ package com.google.android.gms.common.api; -<<<<<<< HEAD -======= import android.accounts.Account; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import org.microg.gms.common.Hide; ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.ApiClientBuilder; @@ -73,6 +70,35 @@ public final class Api { interface NotRequiredOptions extends ApiOptions { } + /** + * {@link ApiOptions} implementation for {@link Api}s that do not take any options. + */ + @PublicApi + final class NoOptions implements NotRequiredOptions { + } + + /** + * Base interface for {@link ApiOptions} that are optional. + */ + @PublicApi + interface Optional extends HasOptions, NotRequiredOptions { + } + + /** + * An interface for {@link ApiOptions} that include an account. + */ + @PublicApi + interface HasAccountOptions extends HasOptions, NotRequiredOptions { + Account getAccount(); + } + + /** + * An interface for {@link ApiOptions} that includes a {@link GoogleSignInAccount} + */ + @PublicApi + interface HasGoogleSignInAccountOptions extends HasOptions { + GoogleSignInAccount getGoogleSignInAccount(); + } } @Hide diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java index ff165974..4a33737c 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApi.java @@ -7,26 +7,19 @@ package com.google.android.gms.common.api; import android.content.Context; -<<<<<<< HEAD -======= import com.google.android.gms.common.api.internal.ApiKey; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import org.microg.gms.common.Hide; ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.GoogleApiManager; +import org.microg.gms.common.api.PendingGoogleApiCall; @PublicApi -<<<<<<< HEAD -public abstract class GoogleApi implements HasApiKey { - @PublicApi(exclude = true) -======= public abstract class GoogleApi implements HasApiKey { private GoogleApiManager manager; @Hide ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) public Api api; @Hide public O options; @@ -34,9 +27,6 @@ public abstract class GoogleApi implements HasApiKey api) { this.api = api; -<<<<<<< HEAD - GoogleApiManager.getInstance(); -======= this.manager = GoogleApiManager.getInstance(context); } @@ -58,7 +48,6 @@ public abstract class GoogleApi implements HasApiKey getApiKey() { return null; ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } @PublicApi(exclude = true) diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java index e5524cb9..b47a2d47 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiActivity.java @@ -1,10 +1,3 @@ -<<<<<<< HEAD -package com.google.android.gms.common.api; - -import android.app.Activity; - -public class GoogleApiActivity extends Activity { -======= /* * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 @@ -30,5 +23,4 @@ public class GoogleApiActivity extends Activity implements DialogInterface.OnCan public void onCancel(DialogInterface dialog) { } ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java index a841fd98..62e52ba3 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java @@ -19,10 +19,15 @@ package com.google.android.gms.common.api; import android.app.Activity; import android.content.Context; import android.os.Bundle; +import android.os.Handler; import android.os.Looper; +import android.view.View; + +import androidx.fragment.app.FragmentActivity; import com.google.android.gms.common.ConnectionResult; +import org.microg.gms.auth.AuthConstants; import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.ApiClientSettings; import org.microg.gms.common.api.GoogleApiClientImpl; @@ -31,6 +36,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * The main entry point for Google Play services integration. @@ -53,6 +59,32 @@ import java.util.Set; @PublicApi @Deprecated public interface GoogleApiClient { + /** + * Connects the client to Google Play services. Blocks until the connection either succeeds or + * fails. This is not allowed on the UI thread. + * + * @return the result of the connection + */ + ConnectionResult blockingConnect(); + + /** + * Connects the client to Google Play services. Blocks until the connection is set or failed or + * has timed out. This is not allowed on the UI thread. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code timeout} argument + * @return the result of the connection + */ + ConnectionResult blockingConnect(long timeout, TimeUnit unit); + + /** + * Clears the account selected by the user and reconnects the client asking the user to pick an + * account again if {@link Builder#useDefaultAccount()} was set. + * + * @return the pending result is fired once the default account has been cleared, but before + * the client is reconnected - for that {@link ConnectionCallbacks} can be used. + */ + PendingResult clearDefaultAccountAndReconnect(); /** * Connects the client to Google Play services. This method returns immediately, and connects @@ -113,6 +145,22 @@ public interface GoogleApiClient { */ public boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener); + /** + * Closes the current connection to Google Play services and creates a new connection. + *

+ * This method closes the current connection then returns immediately and reconnects to the + * service in the background. + *

+ * After calling this method, your application will receive + * {@link ConnectionCallbacks#onConnected(Bundle)} if the connection is successful, or + * {@link OnConnectionFailedListener#onConnectionFailed(ConnectionResult)} if the connection + * failed. + * + * @see #connect() + * @see #disconnect() + */ + void reconnect(); + /** * Registers a listener to receive connection events from this {@link GoogleApiClient}. If the * service is already connected, the listener's {@link ConnectionCallbacks#onConnected(Bundle)} @@ -152,6 +200,19 @@ public interface GoogleApiClient { */ public void registerConnectionFailedListener(OnConnectionFailedListener listener); + /** + * Disconnects the client and stops automatic lifecycle management. Use this before creating a + * new client (which might be necessary when switching accounts, changing the set of used APIs + * etc.). + *

+ * This method must be called from the main thread. + * + * @param lifecycleActivity the activity managing the client's lifecycle. + * @throws IllegalStateException if called from outside of the main thread. + * @see Builder#enableAutoManage(FragmentActivity, int, OnConnectionFailedListener) + */ + void stopAutoManager(FragmentActivity lifecycleActivity) throws IllegalStateException; + /** * Removes a connection listener from this {@link GoogleApiClient}. Note that removing a * listener does not generate any callbacks. @@ -163,15 +224,23 @@ public interface GoogleApiClient { */ void unregisterConnectionCallbacks(ConnectionCallbacks listener); + /** + * Removes a connection failed listener from the {@link GoogleApiClient}. Note that removing a + * listener does not generate any callbacks. + *

+ * If the specified listener is not currently registered to receive connection failed events, + * this method will have no effect. + * + * @param listener the listener to unregister. + */ + void unregisterConnectionFailedListener(OnConnectionFailedListener listener); + + /** + * Builder to configure a {@link GoogleApiClient}. + */ @PublicApi class Builder { private final Context context; -<<<<<<< HEAD - private final Map apis = new HashMap<>(); - private final Set connectionCallbacks = new HashSet<>(); - private final Set connectionFailedListeners = new HashSet<>(); - private final Looper looper; -======= private final Map apis = new HashMap(); private final Set connectionCallbacks = new HashSet(); private final Set connectionFailedListeners = new HashSet(); @@ -183,13 +252,45 @@ public interface GoogleApiClient { private int gravityForPopups; private OnConnectionFailedListener unresolvedConnectionFailedListener; private View viewForPopups; ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) + /** + * Builder to help construct the {@link GoogleApiClient} object. + * + * @param context The context to use for the connection. + */ public Builder(Context context) { this.context = context; this.looper = context.getMainLooper(); } + /** + * Builder to help construct the {@link GoogleApiClient} object. + * + * @param context The context to use for the connection. + * @param connectedListener The listener where the results of the asynchronous + * {@link #connect()} call are delivered. + * @param connectionFailedListener The listener which will be notified if the connection + * attempt fails. + */ + public Builder(Context context, ConnectionCallbacks connectedListener, + OnConnectionFailedListener connectionFailedListener) { + this(context); + addConnectionCallbacks(connectedListener); + addOnConnectionFailedListener(connectionFailedListener); + } + + /** + * Specify which Apis are requested by your app. See {@link Api} for more information. + * + * @param api The Api requested by your app. + * @param options Any additional parameters required for the specific AP + * @see Api + */ + public Builder addApi(Api api, O options) { + apis.put(api, options); + return this; + } + /** * Specify which Apis are requested by your app. See {@link Api} for more information. * @@ -201,14 +302,65 @@ public interface GoogleApiClient { return this; } + /** + * Registers a listener to receive connection events from this {@link GoogleApiClient}. + * Applications should balance calls to this method with calls to + * {@link #unregisterConnectionCallbacks(ConnectionCallbacks)} to avoid + * leaking resources. + *

+ * If the specified listener is already registered to receive connection events, this + * method will not add a duplicate entry for the same listener. + *

+ * Note that the order of messages received here may not be stable, so clients should not + * rely on the order that multiple listeners receive events in. + * + * @param listener the listener where the results of the asynchronous {@link #connect()} + * call are delivered. + */ + public Builder addConnectionCallbacks(ConnectionCallbacks listener) { + connectionCallbacks.add(listener); + return this; + } + + /** + * Adds a listener to register to receive connection failed events from this + * {@link GoogleApiClient}. Applications should balance calls to this method with calls to + * {@link #unregisterConnectionFailedListener(OnConnectionFailedListener)} to avoid + * leaking resources. + *

+ * If the specified listener is already registered to receive connection failed events, + * this method will not add a duplicate entry for the same listener. + *

+ * Note that the order of messages received here may not be stable, so clients should not + * rely on the order that multiple listeners receive events in. + * + * @param listener the listener where the results of the asynchronous {@link #connect()} + * call are delivered. + */ + public Builder addOnConnectionFailedListener(OnConnectionFailedListener listener) { + connectionFailedListeners.add(listener); + return this; + } + + /** + * Specify the OAuth 2.0 scopes requested by your app. See + * {@link com.google.android.gms.common.Scopes} for more information. + * + * @param scope The OAuth 2.0 scopes requested by your app. + * @see com.google.android.gms.common.Scopes + */ + public Builder addScope(Scope scope) { + scopes.add(scope.getScopeUri()); + return this; + } + /** * Builds a new {@link GoogleApiClient} object for communicating with the Google APIs. * * @return The {@link GoogleApiClient} object. */ public GoogleApiClient build() { - int clientId = -1; - return new GoogleApiClientImpl(context, looper, getClientSettings(), apis, connectionCallbacks, connectionFailedListeners); + return new GoogleApiClientImpl(context, looper, getClientSettings(), apis, connectionCallbacks, connectionFailedListeners, clientId); } private ApiClientSettings getClientSettings() { @@ -220,6 +372,66 @@ public interface GoogleApiClient { return clientSettings; } + public Builder enableAutoManage(FragmentActivity fragmentActivity, int cliendId, + OnConnectionFailedListener unresolvedConnectionFailedListener) + throws NullPointerException, IllegalArgumentException, IllegalStateException { + this.fragmentActivity = fragmentActivity; + this.clientId = cliendId; + this.unresolvedConnectionFailedListener = unresolvedConnectionFailedListener; + return this; + } + + /** + * Specify an account name on the device that should be used. If this is never called, the + * client will use the current default account for Google Play services for this + * application. + * + * @param accountName The account name on the device that should be used by + * {@link GoogleApiClient}. + */ + public Builder setAccountName(String accountName) { + this.accountName = accountName; + return this; + } + + /** + * Specifies the part of the screen at which games service popups (for example, + * "welcome back" or "achievement unlocked" popups) will be displayed using gravity. + * + * @param gravityForPopups The gravity which controls the placement of games service popups. + */ + public Builder setGravityForPopups(int gravityForPopups) { + this.gravityForPopups = gravityForPopups; + return this; + } + + /** + * Sets a {@link Handler} to indicate which thread to use when invoking callbacks. Will not + * be used directly to handle callbacks. If this is not called then the application's main + * thread will be used. + */ + public Builder setHandler(Handler handler) { + this.looper = handler.getLooper(); + return this; + } + + /** + * Sets the {@link View} to use as a content view for popups. + * + * @param viewForPopups The view to use as a content view for popups. View cannot be null. + */ + public Builder setViewForPopups(View viewForPopups) { + this.viewForPopups = viewForPopups; + return this; + } + + /** + * Specify that the default account should be used when connecting to services. + */ + public Builder useDefaultAccount() { + this.accountName = AuthConstants.DEFAULT_ACCOUNT; + return this; + } } /** @@ -229,8 +441,6 @@ public interface GoogleApiClient { @PublicApi @Deprecated interface ConnectionCallbacks extends org.microg.gms.common.api.ConnectionCallbacks { -<<<<<<< HEAD -======= /** * A suspension cause informing that the service has been killed. */ @@ -241,9 +451,13 @@ public interface GoogleApiClient { int CAUSE_NETWORK_LOST = 2; void onConnected(Bundle connectionHint); ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } + /** + * Provides callbacks for scenarios that result in a failed attempt to connect the client to + * the service. See {@link ConnectionResult} for a list of error codes and suggestions for + * resolution. + */ @PublicApi @Deprecated interface OnConnectionFailedListener extends org.microg.gms.common.api.OnConnectionFailedListener { diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/HasApiKey.java b/play-services-base/src/main/java/com/google/android/gms/common/api/HasApiKey.java index 642a636d..e208b4d8 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/HasApiKey.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/HasApiKey.java @@ -5,5 +5,8 @@ package com.google.android.gms.common.api; -public interface HasApiKey { +import com.google.android.gms.common.api.internal.ApiKey; + +public interface HasApiKey { + ApiKey getApiKey(); } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java b/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java index 1e5c462f..ec162920 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/PendingResult.java @@ -16,9 +16,6 @@ package com.google.android.gms.common.api; -<<<<<<< HEAD -public interface PendingResult { -======= import java.util.concurrent.TimeUnit; /** @@ -59,5 +56,4 @@ public interface PendingResult { public void setResultCallback(ResultCallback callback, long time, TimeUnit unit); public void setResultCallback(ResultCallback callback); ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/internal/ApiKey.java b/play-services-base/src/main/java/com/google/android/gms/common/api/internal/ApiKey.java index b679b2b9..5a42d8fd 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/api/internal/ApiKey.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/api/internal/ApiKey.java @@ -5,8 +5,8 @@ package com.google.android.gms.common.api.internal; -public class ApiKey { +import com.google.android.gms.common.api.Api; - public ApiKey() { - } +public class ApiKey { + private Api api; } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/data/DataBuffer.java b/play-services-base/src/main/java/com/google/android/gms/common/data/DataBuffer.java index 907aee2d..49c13a99 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/data/DataBuffer.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/data/DataBuffer.java @@ -16,8 +16,6 @@ package com.google.android.gms.common.data; -import androidx.annotation.NonNull; - import com.google.android.gms.common.api.Releasable; import org.microg.gms.common.PublicApi; @@ -30,8 +28,11 @@ import java.util.Iterator; @PublicApi public abstract class DataBuffer implements Releasable, Iterable { + private final DataHolder dataHolder; + @PublicApi(exclude = true) - public DataBuffer() { + public DataBuffer(DataHolder dataHolder) { + this.dataHolder = dataHolder; } /** @@ -42,6 +43,20 @@ public abstract class DataBuffer implements Releasable, Iterable { release(); } + /** + * Get the item at the specified position. Note that the objects returned from subsequent + * invocations of this method for the same position may not be identical objects, but will be + * equal in value. + * + * @param position The position of the item to retrieve. + * @return the item at {@code position} in this buffer. + */ + public abstract T get(int position); + + public int getCount() { + return dataHolder == null ? 0 : dataHolder.getCount(); + } + /** * @deprecated {@link #release()} is idempotent, and so is safe to call multiple times */ @@ -50,7 +65,6 @@ public abstract class DataBuffer implements Releasable, Iterable { return false; } - @NonNull @Override public Iterator iterator() { return null; @@ -64,4 +78,12 @@ public abstract class DataBuffer implements Releasable, Iterable { } + /** + * In order to use this one should correctly override setDataRow(int) in his DataBufferRef + * implementation. Be careful: there will be single DataBufferRef while iterating. + * If you are not sure - DO NOT USE this iterator. + */ + public Iterator singleRefIterator() { + return null; + } } diff --git a/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionCallbacks.java b/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionCallbacks.java new file mode 100644 index 00000000..fdffdac7 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionCallbacks.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package org.microg.gms.common; + +import android.os.Bundle; + +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; + +public final class ForwardConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { + private final GooglePlayServicesClient.ConnectionCallbacks callbacks; + + public ForwardConnectionCallbacks(GooglePlayServicesClient.ConnectionCallbacks callbacks) { + this.callbacks = callbacks; + } + + @Override + public boolean equals(Object o) { + return o instanceof ForwardConnectionCallbacks && + callbacks.equals(((ForwardConnectionCallbacks) o).callbacks); + } + + @Override + public int hashCode() { + return callbacks.hashCode(); + } + + @Override + public void onConnected(Bundle connectionHint) { + callbacks.onConnected(connectionHint); + } + + @Override + public void onConnectionSuspended(int cause) { + callbacks.onDisconnected(); + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionFailedListener.java b/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionFailedListener.java new file mode 100644 index 00000000..1d1f4434 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/ForwardConnectionFailedListener.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package org.microg.gms.common; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; + +public final class ForwardConnectionFailedListener + implements GoogleApiClient.OnConnectionFailedListener { + private final GooglePlayServicesClient.OnConnectionFailedListener listener; + + public ForwardConnectionFailedListener( + GooglePlayServicesClient.OnConnectionFailedListener listener) { + this.listener = listener; + } + + @Override + public boolean equals(Object o) { + return o instanceof ForwardConnectionFailedListener && + listener.equals(((ForwardConnectionFailedListener) o).listener); + } + + @Override + public int hashCode() { + return listener.hashCode(); + } + + @Override + public void onConnectionFailed(ConnectionResult result) { + listener.onConnectionFailed(result); + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java index df6b96e3..15aac9e3 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPendingResult.java @@ -19,15 +19,93 @@ package org.microg.gms.common.api; import android.os.Looper; import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Result; +import com.google.android.gms.common.api.ResultCallback; -public class AbstractPendingResult implements PendingResult { +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class AbstractPendingResult implements PendingResult { + private final Object lock = new Object(); + private final CountDownLatch countDownLatch = new CountDownLatch(1); + private final ResultCallbackHandler handler; + private boolean canceled; + private R result; + private ResultCallback resultCallback; public AbstractPendingResult(Looper looper) { -<<<<<<< HEAD - new ResultCallbackHandler<>(looper); -======= handler = new ResultCallbackHandler(looper); ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } + private R getResult() { + synchronized (lock) { + return result; + } + } + + @Override + public R await() { + try { + countDownLatch.await(); + } catch (InterruptedException ignored) { + } + return getResult(); + } + + @Override + public R await(long time, TimeUnit unit) { + try { + countDownLatch.await(time, unit); + } catch (InterruptedException ignored) { + } + return getResult(); + } + + @Override + public void cancel() { + // TODO + } + + @Override + public boolean isCanceled() { + synchronized (lock) { + return canceled; + } + } + + public boolean isReady() { + return this.countDownLatch.getCount() == 0L; + } + + @Override + public void setResultCallback(ResultCallback callback, long time, TimeUnit unit) { + synchronized (lock) { + if (!isCanceled()) { + if (isReady()) { + handler.sendResultCallback(callback, getResult()); + } else { + handler.sendTimeoutResultCallback(this, unit.toMillis(time)); + } + } + } + } + + @Override + public void setResultCallback(ResultCallback callback) { + synchronized (lock) { + if (!isCanceled()) { + if (isReady()) { + handler.sendResultCallback(callback, getResult()); + } else { + resultCallback = callback; + } + } + } + } + + public void deliverResult(R result) { + this.result = result; + countDownLatch.countDown(); + + } } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPlayServicesClient.java b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPlayServicesClient.java new file mode 100644 index 00000000..eb9d66bd --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/AbstractPlayServicesClient.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package org.microg.gms.common.api; + +import android.util.Log; + +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; + +import org.microg.gms.common.ForwardConnectionCallbacks; +import org.microg.gms.common.ForwardConnectionFailedListener; + +public class AbstractPlayServicesClient implements GooglePlayServicesClient { + private static final String TAG = "GmsPlayServicesClient"; + + protected final GoogleApiClient googleApiClient; + + public AbstractPlayServicesClient(GoogleApiClient googleApiClient) { + this.googleApiClient = googleApiClient; + } + + public void assertConnected() { + if (!isConnected()) throw new IllegalStateException("Not connected!"); + } + + @Override + public void connect() { + Log.d(TAG, "connect()"); + googleApiClient.connect(); + } + + @Override + public void disconnect() { + Log.d(TAG, "disconnect()"); + //TODO googleApiClient.disconnect(); + } + + @Override + public boolean isConnected() { + return googleApiClient.isConnected(); + } + + @Override + public boolean isConnecting() { + return googleApiClient.isConnecting(); + } + + @Override + public void registerConnectionCallbacks(final ConnectionCallbacks listener) { + googleApiClient.registerConnectionCallbacks(new ForwardConnectionCallbacks(listener)); + } + + @Override + public boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener) { + return googleApiClient + .isConnectionCallbacksRegistered(new ForwardConnectionCallbacks(listener)); + } + + @Override + public void unregisterConnectionCallbacks( + ConnectionCallbacks listener) { + googleApiClient.unregisterConnectionCallbacks(new ForwardConnectionCallbacks(listener)); + } + + @Override + public void registerConnectionFailedListener( + OnConnectionFailedListener listener) { + googleApiClient.registerConnectionFailedListener( + new ForwardConnectionFailedListener(listener)); + } + + @Override + public boolean isConnectionFailedListenerRegistered( + OnConnectionFailedListener listener) { + return googleApiClient.isConnectionFailedListenerRegistered( + new ForwardConnectionFailedListener(listener)); + } + + @Override + public void unregisterConnectionFailedListener( + OnConnectionFailedListener listener) { + googleApiClient.unregisterConnectionFailedListener( + new ForwardConnectionFailedListener(listener)); + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java index 2e023fde..2dffa374 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiClientImpl.java @@ -20,30 +20,25 @@ import android.content.Context; import android.os.Bundle; import android.os.Looper; -<<<<<<< HEAD -======= import androidx.fragment.app.FragmentActivity; import android.util.Log; import com.google.android.gms.common.ConnectionResult; ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Status; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; public class GoogleApiClientImpl implements GoogleApiClient { private static final String TAG = "GmsApiClientImpl"; -<<<<<<< HEAD - private final Map apiConnections = new HashMap<>(); - private final Set connectionCallbacks = new HashSet<>(); - private final Set connectionFailedListeners = new HashSet<>(); -======= private final Context context; private final Looper looper; private final ApiClientSettings clientSettings; @@ -80,50 +75,29 @@ public class GoogleApiClientImpl implements GoogleApiClient { } }; private int usageCounter = 0; ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) private boolean shouldDisconnect = false; public GoogleApiClientImpl(Context context, Looper looper, ApiClientSettings clientSettings, Map apis, Set connectionCallbacks, - Set connectionFailedListeners) { + Set connectionFailedListeners, int clientId) { + this.context = context; + this.looper = looper; + this.clientSettings = clientSettings; + this.apis.putAll(apis); this.connectionCallbacks.addAll(connectionCallbacks); this.connectionFailedListeners.addAll(connectionFailedListeners); + this.clientId = clientId; if (this.clientSettings.sessionId == null) { this.clientSettings.sessionId = hashCode(); } for (Api api : apis.keySet()) { - ConnectionCallbacks baseConnectionCallbacks = new ConnectionCallbacks() { - @Override - public void onConnected(Bundle connectionHint) { - Log.d(TAG, "ConnectionCallbacks : onConnected()"); - for (ConnectionCallbacks callback : connectionCallbacks) { - callback.onConnected(connectionHint); - } - } - - @Override - public void onConnectionSuspended(int cause) { - Log.d(TAG, "ConnectionCallbacks : onConnectionSuspended()"); - for (ConnectionCallbacks callback : connectionCallbacks) { - callback.onConnectionSuspended(cause); - } - } - }; - OnConnectionFailedListener baseConnectionFailedListener = result -> { - Log.d(TAG, "OnConnectionFailedListener : onConnectionFailed()"); - for (OnConnectionFailedListener listener : connectionFailedListeners) { - listener.onConnectionFailed(result); - } - }; apiConnections.put(api, api.getBuilder().build(apis.get(api), context, looper, clientSettings, baseConnectionCallbacks, baseConnectionFailedListener)); } } -<<<<<<< HEAD -======= public synchronized void incrementUsageCounter() { usageCounter++; } @@ -156,7 +130,6 @@ public class GoogleApiClientImpl implements GoogleApiClient { return null; } ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) @Override public synchronized void connect() { Log.d(TAG, "connect()"); @@ -177,12 +150,6 @@ public class GoogleApiClientImpl implements GoogleApiClient { @Override public synchronized void disconnect() { -<<<<<<< HEAD - Log.d(TAG, "disconnect()"); - for (ApiClient connection : apiConnections.values()) { - if (connection.isConnected()) { - connection.disconnect(); -======= if (usageCounter > 0) { shouldDisconnect = true; } else { @@ -191,7 +158,6 @@ public class GoogleApiClientImpl implements GoogleApiClient { if (connection.isConnected()) { connection.disconnect(); } ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } } } @@ -223,6 +189,13 @@ public class GoogleApiClientImpl implements GoogleApiClient { return connectionFailedListeners.contains(listener); } + @Override + public synchronized void reconnect() { + Log.d(TAG, "reconnect()"); + disconnect(); + connect(); + } + @Override public void registerConnectionCallbacks(ConnectionCallbacks listener) { connectionCallbacks.add(listener); @@ -233,9 +206,18 @@ public class GoogleApiClientImpl implements GoogleApiClient { connectionFailedListeners.add(listener); } + @Override + public void stopAutoManager(FragmentActivity lifecycleActivity) throws IllegalStateException { + + } + @Override public void unregisterConnectionCallbacks(ConnectionCallbacks listener) { connectionCallbacks.remove(listener); } + @Override + public void unregisterConnectionFailedListener(OnConnectionFailedListener listener) { + connectionFailedListeners.remove(listener); + } } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java index d30293ed..5cf7933e 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java @@ -5,9 +5,6 @@ package org.microg.gms.common.api; -<<<<<<< HEAD -import android.annotation.SuppressLint; -======= import android.content.Context; import android.os.Bundle; @@ -21,27 +18,22 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) public class GoogleApiManager { - @SuppressLint("StaticFieldLeak") private static GoogleApiManager instance; -<<<<<<< HEAD -======= private Context context; private Map clientMap = new HashMap<>(); private Map>> waitingApiCallMap = new HashMap<>(); ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) - private GoogleApiManager() { + private GoogleApiManager(Context context) { + this.context = context; } - public synchronized static void getInstance() { - if (instance == null) instance = new GoogleApiManager(); + public synchronized static GoogleApiManager getInstance(Context context) { + if (instance == null) instance = new GoogleApiManager(context); + return instance; } -<<<<<<< HEAD -======= private synchronized A clientForApi(GoogleApi api) { ApiInstance apiInstance = new ApiInstance(api); if (clientMap.containsKey(apiInstance)) { @@ -194,5 +186,4 @@ public class GoogleApiManager { return result; } } ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) } diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/InstantPendingResult.java b/play-services-base/src/main/java/org/microg/gms/common/api/InstantPendingResult.java new file mode 100644 index 00000000..ba75725c --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/common/api/InstantPendingResult.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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. + */ + +package org.microg.gms.common.api; + +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Result; +import com.google.android.gms.common.api.ResultCallback; + +import java.util.concurrent.TimeUnit; + +public class InstantPendingResult implements PendingResult { + R value; + + public InstantPendingResult(R value) { + this.value = value; + } + + @Override + public R await() { + return value; + } + + @Override + public R await(long time, TimeUnit unit) { + return value; + } + + @Override + public void cancel() { + + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void setResultCallback(ResultCallback callback, long time, TimeUnit unit) { + callback.onResult(value); + } + + @Override + public void setResultCallback(ResultCallback callback) { + callback.onResult(value); + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java b/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java index 3401f596..c19c849c 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/ResultCallbackHandler.java @@ -47,8 +47,6 @@ class ResultCallbackHandler extends Handler { } } -<<<<<<< HEAD -======= public void sendResultCallback(ResultCallback callback, R result) { Message message = new Message(); message.what = CALLBACK_ON_COMPLETE; @@ -60,7 +58,6 @@ class ResultCallbackHandler extends Handler { } ->>>>>>> 49892e88 (Restructure modules https://github.com/microg/GmsCore/commit/18c3a6154dc41cebe1d4aeda5592b8f23928356f) public static class OnCompleteObject { public ResultCallback callback; public R result; diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index 7ec7d753..333ca3ac 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -22,8 +22,8 @@ dependencies { // Dependencies from play-services-basement:18.0.0 api "androidx.collection:collection:$collectionVersion" - api "androidx.core:core-ktx:$coreVersion" - api "androidx.fragment:fragment-ktx:$fragmentVersion" + api "androidx.core:core:$coreVersion" + api "androidx.fragment:fragment:$fragmentVersion" } android { @@ -44,7 +44,6 @@ android { buildFeatures { viewBinding = true - aidl true } sourceSets { @@ -52,8 +51,7 @@ android { } compileOptions { - sourceCompatibility = 17 - targetCompatibility = 17 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 } - namespace 'org.microg.gms.basement' } \ No newline at end of file diff --git a/play-services-basement/src/main/AndroidManifest.xml b/play-services-basement/src/main/AndroidManifest.xml new file mode 100644 index 00000000..bd436773 --- /dev/null +++ b/play-services-basement/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/ConnectionResult.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/ConnectionResult.aidl new file mode 100644 index 00000000..e46e6d5d --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/ConnectionResult.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.common; + +parcelable ConnectionResult; \ No newline at end of file diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupQuery.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupQuery.aidl new file mode 100644 index 00000000..fab15aa1 --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupQuery.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.common; + +parcelable GoogleCertificatesLookupQuery; \ No newline at end of file diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupResponse.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupResponse.aidl new file mode 100644 index 00000000..e9b4bff3 --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesLookupResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.common; + +parcelable GoogleCertificatesLookupResponse; \ No newline at end of file diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesQuery.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesQuery.aidl new file mode 100644 index 00000000..e6aedfef --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/GoogleCertificatesQuery.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.common; + +parcelable GoogleCertificatesQuery; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl rename to play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ICertData.aidl diff --git a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl new file mode 100644 index 00000000..6d54ad26 --- /dev/null +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGoogleCertificatesApi.aidl @@ -0,0 +1,18 @@ +package com.google.android.gms.common.internal; + +import com.google.android.gms.common.GoogleCertificatesLookupQuery; +import com.google.android.gms.common.GoogleCertificatesLookupResponse; +import com.google.android.gms.common.GoogleCertificatesQuery; +import com.google.android.gms.dynamic.IObjectWrapper; + +interface IGoogleCertificatesApi { + IObjectWrapper getGoogleCertificates() = 0; + IObjectWrapper getGoogleReleaseCertificates() = 1; + boolean isGoogleReleaseSigned(String packageName, IObjectWrapper certData) = 2; + boolean isGoogleSigned(String packageName, IObjectWrapper certData) = 3; + boolean isGoogleOrPlatformSigned(in GoogleCertificatesQuery query, IObjectWrapper packageManager) = 4; + GoogleCertificatesLookupResponse isPackageGoogleOrPlatformSigned(in GoogleCertificatesLookupQuery query) = 5; + boolean isPackageGoogleOrPlatformSignedAvailable() = 6; + GoogleCertificatesLookupResponse queryPackageSigned(in GoogleCertificatesLookupQuery query) = 7; + boolean isFineGrainedPackageVerificationAvailable() = 8; +} \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl rename to play-services-basement/src/main/aidl/com/google/android/gms/common/internal/ISignInButtonCreator.aidl diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/ConnectionResult.java b/play-services-basement/src/main/java/com/google/android/gms/common/ConnectionResult.java index 23ebfb81..f3272de8 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/ConnectionResult.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/ConnectionResult.java @@ -18,6 +18,7 @@ package com.google.android.gms.common; import android.app.Activity; import android.app.PendingIntent; +import android.content.Intent; import android.content.IntentSender; import android.text.TextUtils; @@ -238,6 +239,35 @@ public class ConnectionResult { } } + /** + * Indicates the type of error that interrupted connection. + * + * @return the error code, or {@link #SUCCESS} if no error occurred. + */ + public int getErrorCode() { + return statusCode; + } + + /** + * Returns an error message for connection result. + * + * @return the message + */ + public String getErrorMessage() { + return message; + } + + /** + * A pending intent to resolve the connection failure. This intent can be started with + * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to + * present UI to solve the issue. + * + * @return The pending intent to resolve the connection failure. + */ + public PendingIntent getResolution() { + return pendingIntent; + } + @Override public int hashCode() { return Arrays.hashCode(new Object[]{statusCode, pendingIntent, message}); @@ -253,6 +283,15 @@ public class ConnectionResult { return statusCode != 0 && pendingIntent != null; } + /** + * Returns {@code true} if the connection was successful. + * + * @return {@code true} if the connection was successful, {@code false} if there was an error. + */ + public boolean isSuccess() { + return statusCode == 0; + } + /** * Resolves an error by starting any intents requiring user interaction. See * {@link #SIGN_IN_REQUIRED}, and {@link #RESOLUTION_REQUIRED}. diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupQuery.java b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupQuery.java new file mode 100644 index 00000000..ab8da97a --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupQuery.java @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common; + +import android.content.Context; +import android.os.Parcel; +import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.dynamic.IObjectWrapper; +import com.google.android.gms.dynamic.ObjectWrapper; + +@SafeParcelable.Class +public class GoogleCertificatesLookupQuery extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getCallingPackage") + String callingPackage; + @Field(2) + boolean allowTestKeys; + @Field(3) + boolean ignoreTestKeysOverride; + @Field(4) + IObjectWrapper contextWrapper; + private Context context; + @Field(5) + boolean isChimeraPackage; + @Field(6) + boolean includeHashesInErrorMessage; + + public String getCallingPackage() { + return callingPackage; + } + + public Context getContext() { + if (context == null && contextWrapper != null) { + context = ObjectWrapper.unwrapTyped(contextWrapper, Context.class); + } + return context; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GoogleCertificatesLookupQuery.class); +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupResponse.java b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupResponse.java new file mode 100644 index 00000000..90e0ee3a --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesLookupResponse.java @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common; + +import android.os.Parcel; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GoogleCertificatesLookupResponse extends AbstractSafeParcelable { + @Field(1) + public final boolean result; + @Field(2) + public final String errorMessage; + @Field(3) + public final int statusValue; + @Field(4) + public final int firstPartyStatusValue; + + @Constructor + public GoogleCertificatesLookupResponse(@Param(1) boolean result, @Param(2) String errorMessage, @Param(3) int statusValue, @Param(4) int firstPartyStatusValue) { + this.result = result; + this.errorMessage = errorMessage; + this.statusValue = statusValue; + this.firstPartyStatusValue = firstPartyStatusValue; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + CREATOR.writeToParcel(this, out, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GoogleCertificatesLookupResponse.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/common/internal/GoogleCertificatesQuery.java b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesQuery.java similarity index 53% rename from play-services-api/src/main/java/com/google/android/gms/common/internal/GoogleCertificatesQuery.java rename to play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesQuery.java index 02dcc3e9..dce40a90 100644 --- a/play-services-api/src/main/java/com/google/android/gms/common/internal/GoogleCertificatesQuery.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/GoogleCertificatesQuery.java @@ -1,47 +1,38 @@ /* - * Copyright (C) 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: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ -package com.google.android.gms.common.internal; +package com.google.android.gms.common; import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; - +import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.CertData; +import com.google.android.gms.common.internal.ICertData; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; +import org.microg.gms.common.Hide; -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; - -public class GoogleCertificatesQuery extends AutoSafeParcelable { - @SafeParceled(1) - private final String packageName; - @SafeParceled(2) - private final IBinder certDataBinder; +@Hide +@SafeParcelable.Class +public class GoogleCertificatesQuery extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getCallingPackage") + String callingPackage; + @Field(2) + IBinder certDataBinder; private CertData certData; - @SafeParceled(3) - private final boolean allowNonRelease; + @Field(3) + boolean allowTestKeys; + @Field(4) + boolean ignoreTestKeysOverride; - public GoogleCertificatesQuery(String packageName, IBinder certDataBinder, boolean allowNonRelease) { - this.packageName = packageName; - this.certDataBinder = certDataBinder; - this.allowNonRelease = allowNonRelease; - } - - public String getPackageName() { - return packageName; + public String getCallingPackage() { + return callingPackage; } public CertData getCertData() { @@ -77,5 +68,10 @@ public class GoogleCertificatesQuery extends AutoSafeParcelable { return certData; } - public static final Creator CREATOR = new AutoCreator(GoogleCertificatesQuery.class); + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GoogleCertificatesQuery.class); } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/Scopes.java b/play-services-basement/src/main/java/com/google/android/gms/common/Scopes.java new file mode 100644 index 00000000..520bef02 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/Scopes.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2013-2017 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. + */ + +package com.google.android.gms.common; + +public class Scopes { + public static final String PROFILE = "profile"; + public static final String APP_STATE = "https://www.googleapis.com/auth/appstate"; +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/CommonStatusCodes.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/CommonStatusCodes.java index 984c6fce..6a2cbb51 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/CommonStatusCodes.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/CommonStatusCodes.java @@ -16,10 +16,13 @@ package com.google.android.gms.common.api; +import androidx.annotation.NonNull; + import org.microg.gms.common.PublicApi; @PublicApi public class CommonStatusCodes { + public static final int SUCCESS_CACHE = -1; public static final int SUCCESS = 0; @Deprecated public static final int SERVICE_MISSING = 1; @@ -28,10 +31,61 @@ public class CommonStatusCodes { @Deprecated public static final int SERVICE_DISABLED = 3; public static final int SIGN_IN_REQUIRED = 4; + public static final int INVALID_ACCOUNT = 5; public static final int RESOLUTION_REQUIRED = 6; + public static final int NETWORK_ERROR = 7; public static final int INTERNAL_ERROR = 8; + public static final int SERVICE_INVALID = 9; + public static final int DEVELOPER_ERROR = 10; + public static final int LICENSE_CHECK_FAILED = 11; public static final int ERROR = 13; public static final int INTERRUPTED = 14; + public static final int TIMEOUT = 15; public static final int CANCELED = 16; + public static final int API_NOT_CONNECTED = 17; + public static final int DEAD_CLIENT = 18; + @NonNull + public static String getStatusCodeString(int statusCode) { + switch (statusCode) { + case SUCCESS_CACHE: + return "SUCCESS_CACHE"; + case SUCCESS: + return "SUCCESS"; + case SERVICE_VERSION_UPDATE_REQUIRED: + return "SERVICE_VERSION_UPDATE_REQUIRED"; + case SERVICE_DISABLED: + return "SERVICE_DISABLED"; + case SIGN_IN_REQUIRED: + return "SIGN_IN_REQUIRED"; + case INVALID_ACCOUNT: + return "INVALID_ACCOUNT"; + case RESOLUTION_REQUIRED: + return "RESOLUTION_REQUIRED"; + case NETWORK_ERROR: + return "NETWORK_ERROR"; + case INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case SERVICE_INVALID: + return "SERVICE_INVALID"; + case DEVELOPER_ERROR: + return "DEVELOPER_ERROR"; + case LICENSE_CHECK_FAILED: + return "LICENSE_CHECK_FAILED"; + case ERROR: + return "ERROR"; + case INTERRUPTED: + return "INTERRUPTED"; + case TIMEOUT: + return "TIMEOUT"; + case CANCELED: + return "CANCELED"; + case API_NOT_CONNECTED: + return "API_NOT_CONNECTED"; + case DEAD_CLIENT: + return "DEAD_CLIENT"; + default: + return "unknown status code: " + statusCode; + } + } } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/Scope.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/Scope.java index a90803e7..60bc1fb9 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/Scope.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/Scope.java @@ -16,8 +16,6 @@ package com.google.android.gms.common.api; -import androidx.annotation.NonNull; - import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; @@ -46,22 +44,20 @@ public class Scope extends AutoSafeParcelable { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Scope)) return false; - assert scopeUri != null; - return scopeUri.equals(((Scope) o).scopeUri); + return this == o || o instanceof Scope && scopeUri.equals(((Scope) o).scopeUri); + } + + public String getScopeUri() { + return scopeUri; } @Override public int hashCode() { - assert scopeUri != null; return scopeUri.hashCode(); } - @NonNull @Override public String toString() { - assert scopeUri != null; return scopeUri; } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java index 433ef043..7b936e67 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java @@ -16,7 +16,11 @@ package com.google.android.gms.common.api; +import android.app.Activity; import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; @@ -40,8 +44,16 @@ public final class Status extends AutoSafeParcelable implements Result { @SafeParceled(1) private final int statusCode; + @SafeParceled(2) + private final String statusMessage; + + @SafeParceled(3) + private final PendingIntent resolution; + private Status() { statusCode = 0; + statusMessage = null; + resolution = null; } /** @@ -72,6 +84,19 @@ public final class Status extends AutoSafeParcelable implements Result { */ public Status(int statusCode, String statusMessage, PendingIntent resolution) { this.statusCode = statusCode; + this.statusMessage = statusMessage; + this.resolution = resolution; + } + + /** + * A pending intent to resolve the failure. This intent can be started with + * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to + * present UI to solve the issue. + * + * @return The pending intent to resolve the failure. + */ + public PendingIntent getResolution() { + return resolution; } /** @@ -86,6 +111,30 @@ public final class Status extends AutoSafeParcelable implements Result { return this; } + /** + * Indicates the status of the operation. + * + * @return Status code resulting from the operation. The value is one of the constants in + * {@link CommonStatusCodes} or specific to the APIs added to the GoogleApiClient. + */ + public int getStatusCode() { + return statusCode; + } + + public String getStatusMessage() { + return statusMessage; + } + + /** + * Returns true if calling {@link #startResolutionForResult(Activity, int)} will start any + * intents requiring user interaction. + * + * @return true if there is a resolution that can be started. + */ + public boolean hasResolution() { + return resolution != null; + } + /** * Returns true if the operation was canceled. */ @@ -93,5 +142,39 @@ public final class Status extends AutoSafeParcelable implements Result { return statusCode == CommonStatusCodes.CANCELED; } + /** + * Returns true if the operation was interrupted. + */ + public boolean isInterrupted() { + return statusCode == CommonStatusCodes.INTERRUPTED; + } + + /** + * Returns true if the operation was successful. + * + * @return true if the operation was successful, false if there was an error. + */ + public boolean isSuccess() { + return statusCode <= 0; + } + + /** + * Resolves an error by starting any intents requiring user interaction. See + * {@link CommonStatusCodes#SIGN_IN_REQUIRED}, and {@link CommonStatusCodes#RESOLUTION_REQUIRED}. + * + * @param activity An Activity context to use to resolve the issue. The activity's + * onActivityResult method will be invoked after the user is done. If the + * resultCode is {@link Activity#RESULT_OK}, the application should try to + * connect again. + * @param requestCode The request code to pass to onActivityResult. + * @throws SendIntentException If the resolution intent has been canceled or is no longer able + * to execute the request. + */ + public void startResolutionForResult(Activity activity, int requestCode) throws SendIntentException { + if (hasResolution()) { + activity.startIntentSenderForResult(resolution.getIntentSender(), requestCode, null, 0, 0, 0); + } + } + public static final Creator CREATOR = new AutoCreator<>(Status.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/common/internal/CertData.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/CertData.java similarity index 93% rename from play-services-api/src/main/java/com/google/android/gms/common/internal/CertData.java rename to play-services-basement/src/main/java/com/google/android/gms/common/internal/CertData.java index 62b1abfd..826d1c37 100644 --- a/play-services-api/src/main/java/com/google/android/gms/common/internal/CertData.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/CertData.java @@ -55,12 +55,12 @@ public class CertData extends ICertData.Stub { } @Override - public IObjectWrapper getWrappedBytes() { + public IObjectWrapper getWrappedBytes() throws RemoteException { return ObjectWrapper.wrap(getBytes()); } @Override - public int remoteHashCode() { + public int remoteHashCode() throws RemoteException { return hashCode(); } } diff --git a/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java b/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java index ce938bbc..4bc96034 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java +++ b/play-services-basement/src/main/java/com/google/android/gms/dynamic/ObjectWrapper.java @@ -41,37 +41,7 @@ public class ObjectWrapper extends IObjectWrapper.Stub { } IBinder binder = obj.asBinder(); -<<<<<<< HEAD - Field[] fields = binder.getClass().getDeclaredFields(); - - if (fields.length < 1) { - throw new IllegalArgumentException("No fields were found"); - } - - // Ignore synthetic field(s) from JaCoCo or elsewhere - // https://www.jacoco.org/jacoco/trunk/doc/faq.html - - @Nullable - Field field = null; - - for (Field currentField : fields) { - if (currentField.isSynthetic()) { - continue; - } - - if (field == null) { - field = currentField; - } else { - throw new IllegalArgumentException("Too many non-synthetic fields were found"); - } - } - - if (field == null) { - throw new IllegalArgumentException("No non-synthetic fields were found"); - } -======= Field field = getField(binder); ->>>>>>> 42253b04 (Allow synthetic field(s) in ObjectWrapper https://github.com/microg/GmsCore/commit/bc6bc583b9ebce160faab449cb62d947b5d791b9) if (!field.isAccessible()) { field.setAccessible(true); @@ -133,4 +103,4 @@ public class ObjectWrapper extends IObjectWrapper.Stub { public static ObjectWrapper wrap(T t) { return new ObjectWrapper<>(t); } -} \ No newline at end of file +} diff --git a/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java b/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java index 21a5e68a..6cac0d45 100644 --- a/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java +++ b/play-services-basement/src/main/java/org/microg/gms/auth/AuthConstants.java @@ -19,6 +19,7 @@ package org.microg.gms.auth; import org.microg.gms.basement.BuildConfig; public class AuthConstants { + public static final String DEFAULT_ACCOUNT = "<>"; public static final String SCOPE_GET_ACCOUNT_ID = "^^_account_id_^^"; public static final String PROVIDER_METHOD_GET_ACCOUNTS = "get_accounts"; public static final String PROVIDER_METHOD_CLEAR_PASSWORD = "clear_password"; diff --git a/play-services-basement/src/main/java/org/microg/gms/common/Constants.java b/play-services-basement/src/main/java/org/microg/gms/common/Constants.java index fd31e054..a905f2bc 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/Constants.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/Constants.java @@ -21,13 +21,8 @@ import org.microg.gms.basement.BuildConfig; public class Constants { public static final int GMS_VERSION_CODE = (BuildConfig.VERSION_CODE / 1000) * 1000; public static final String GMS_PACKAGE_NAME = "com.google.android.gms"; -<<<<<<< HEAD -======= public static final String GSF_PACKAGE_NAME = BuildConfig.BASE_PACKAGE_NAME + ".android.gsf"; ->>>>>>> 25329625 (Set basePackageName) public static final String GMS_PACKAGE_SIGNATURE_SHA1 = "38918a453d07199354f8b19af05ec6562ced5788"; - public static final String GMS_SECONDARY_PACKAGE_SIGNATURE_SHA1 = "bd32424203e0fb25f36b57e5aa356f9bdd1da998"; - @Deprecated public static final int MAX_REFERENCE_VERSION = GMS_VERSION_CODE; } 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 375e4de2..efdbb1d2 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 @@ -171,9 +171,9 @@ public enum GmsService { APP_ERRORS(334, "com.google.android.gms.apperrors.service.START_APP_ERROR"), ; - public final int SERVICE_ID; - public final String ACTION; - public final String[] SECONDARY_ACTIONS; + public int SERVICE_ID; + public String ACTION; + public String[] SECONDARY_ACTIONS; GmsService(int serviceId, String... actions) { this.SERVICE_ID = serviceId; @@ -181,6 +181,11 @@ public enum GmsService { this.SECONDARY_ACTIONS = actions; } + public interface ADVERTISING_ID { + // Has no service id + String ACTION = "com.google.android.gms.ads.identifier.service.START"; + } + public static GmsService byServiceId(int serviceId) { for (GmsService service : values()) { if (service.SERVICE_ID == serviceId) return service; @@ -188,6 +193,15 @@ public enum GmsService { return UNKNOWN; } + public static GmsService byAction(String action) { + for (GmsService service : values()) { + for (String serviceAction : service.SECONDARY_ACTIONS) { + if (serviceAction.equals(action)) return service; + } + } + return UNKNOWN; + } + public static String nameFromServiceId(int serviceId) { return byServiceId(serviceId).toString(serviceId); } diff --git a/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt b/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt index 27996b8f..06c131ab 100644 --- a/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt +++ b/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt @@ -14,5 +14,9 @@ * limitations under the License. */ -package com.google.android.gms.dynamic +package org.microg.gms.kotlin +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper + +inline fun IObjectWrapper?.unwrap(): T? = ObjectWrapper.unwrapTyped(this, T::class.java) diff --git a/play-services-core-proto/build.gradle b/play-services-core-proto/build.gradle index 6edfcb4b..2cfb351e 100644 --- a/play-services-core-proto/build.gradle +++ b/play-services-core-proto/build.gradle @@ -21,9 +21,9 @@ sourceSets { } compileKotlin { - kotlinOptions.jvmTarget = 17 + kotlinOptions.jvmTarget = 1.8 } compileTestKotlin { - kotlinOptions.jvmTarget = 17 + kotlinOptions.jvmTarget = 1.8 } \ No newline at end of file diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 01d928ca..5cb9aedd 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -97,12 +97,9 @@ android { } } -<<<<<<< HEAD -======= lintOptions { disable 'MissingTranslation', 'GetLocales', 'BatteryLife', 'ImpliedQuantity', 'MissingQuantity', 'InvalidWakeLockTag', 'Range' } ->>>>>>> a907c391 (Build: Fix GetLocales lint https://github.com/microg/GmsCore/commit/f0b15d275a0c4ec3625033d651414d396e368320) buildTypes { release { @@ -112,22 +109,16 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } packagingOptions { + exclude 'META-INF/ASL2.0' jniLibs { useLegacyPackaging true } - resources { - excludes += ['META-INF/ASL2.0'] - } } - lint { - disable 'MissingTranslation', 'BatteryLife', 'ImpliedQuantity', 'MissingQuantity', 'InvalidWakeLockTag', 'Range' - } - namespace 'com.mgoogle.android.gms' } if (file('user.gradle').exists()) { diff --git a/play-services-core/microg-ui-tools/build.gradle b/play-services-core/microg-ui-tools/build.gradle index 039e97f1..b6b2018d 100644 --- a/play-services-core/microg-ui-tools/build.gradle +++ b/play-services-core/microg-ui-tools/build.gradle @@ -28,21 +28,14 @@ android { } compileOptions { - sourceCompatibility = 11 - targetCompatibility = 11 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 } -<<<<<<< HEAD - lint { - disable 'MissingTranslation' -======= lintOptions { // TODO: Remove MissingTranslation once we have stable strings and proper translations. disable 'MissingTranslation', 'GetLocales' ->>>>>>> a907c391 (Build: Fix GetLocales lint https://github.com/microg/GmsCore/commit/f0b15d275a0c4ec3625033d651414d396e368320) } - namespace 'org.microg.tools.ui' - } dependencies { diff --git a/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml b/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml new file mode 100644 index 00000000..004f299c --- /dev/null +++ b/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java index b77ea4ce..bb065aca 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java @@ -29,7 +29,6 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -65,7 +64,7 @@ public abstract class AbstractAboutFragment extends Fragment { } protected String getAppName() { - return getAppName(requireContext()); + return getAppName(getContext()); } public static String getLibVersion(String packageName) { @@ -83,7 +82,7 @@ public abstract class AbstractAboutFragment extends Fragment { } protected String getSelfVersion() { - return getSelfVersion(requireContext()); + return getSelfVersion(getContext()); } protected String getSummary() { @@ -94,7 +93,7 @@ public abstract class AbstractAboutFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View aboutRoot = inflater.inflate(R.layout.about_root, container, false); - ((ImageView) aboutRoot.findViewById(android.R.id.icon)).setImageDrawable(getIcon(requireContext())); + ((ImageView) aboutRoot.findViewById(android.R.id.icon)).setImageDrawable(getIcon(getContext())); ((TextView) aboutRoot.findViewById(android.R.id.title)).setText(getAppName()); ((TextView) aboutRoot.findViewById(R.id.about_version)).setText(getString(R.string.about_version_str, getSelfVersion())); String summary = getSummary(); @@ -137,7 +136,6 @@ public abstract class AbstractAboutFragment extends Fragment { this.copyright = copyright; } - @NonNull @Override public String toString() { return name + ", " + copyright; diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java index 85bc123f..30bf98c8 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java @@ -66,6 +66,22 @@ public abstract class AbstractDashboardActivity extends AppCompatActivity { resetConditionViews(); } + protected void addAllConditions(Condition[] conditions) { + for (Condition condition : conditions) { + addCondition(condition); + } + } + + protected void addCondition(Condition condition) { + conditions.add(condition); + if (conditionContainer == null) return; + if (condition.isEvaluated()) { + addConditionToView(condition); + } else { + evaluateConditionAsync(condition); + } + } + private synchronized void addConditionToView(Condition condition) { for (int i = 0; i < conditionContainer.getChildCount(); i++) { if (conditionContainer.getChildAt(i).getTag() == condition) return; @@ -73,6 +89,11 @@ public abstract class AbstractDashboardActivity extends AppCompatActivity { conditionContainer.addView(condition.createView(this, conditionContainer)); } + protected void clearConditions() { + conditions.clear(); + resetConditionViews(); + } + protected Fragment getFragment() { if (preferencesResource == 0) { throw new IllegalStateException("Neither preferencesResource given, nor overriden getFragment()"); diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java index db1da7b2..a771a9ea 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java @@ -6,12 +6,12 @@ import android.view.ViewGroup; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; - -import java.util.Objects; +import androidx.fragment.app.FragmentTransaction; public abstract class AbstractSettingsActivity extends AppCompatActivity { protected boolean showHomeAsUp = false; protected int preferencesResource = 0; + private ViewGroup customBarContainer; protected int customBarLayout = 0; protected SwitchBar switchBar; @@ -20,12 +20,12 @@ public abstract class AbstractSettingsActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.settings_activity); if (showHomeAsUp) { - Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); } switchBar = (SwitchBar) findViewById(R.id.switch_bar); - ViewGroup customBarContainer = (ViewGroup) findViewById(R.id.custom_bar); + customBarContainer = (ViewGroup) findViewById(R.id.custom_bar); if (customBarLayout != 0) { customBarContainer.addView(getLayoutInflater().inflate(customBarLayout, customBarContainer, false)); } @@ -35,6 +35,26 @@ public abstract class AbstractSettingsActivity extends AppCompatActivity { .commit(); } + public void setCustomBarLayout(int layout) { + customBarLayout = layout; + if (customBarContainer != null) { + customBarContainer.removeAllViews(); + customBarContainer.addView(getLayoutInflater().inflate(customBarLayout, customBarContainer, false)); + } + } + + public SwitchBar getSwitchBar() { + return switchBar; + } + + public void replaceFragment(Fragment fragment) { + getSupportFragmentManager().beginTransaction() + .addToBackStack("root") + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .replace(R.id.content_wrapper, fragment) + .commit(); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java index 78ac70b0..0f1967a5 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java @@ -16,7 +16,6 @@ package org.microg.tools.ui; -import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; @@ -27,11 +26,10 @@ public abstract class AbstractSettingsFragment extends PreferenceFragmentCompat private static final String DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG"; @Override - public void onDisplayPreferenceDialog(@NonNull Preference preference) { + public void onDisplayPreferenceDialog(Preference preference) { if (preference instanceof DialogPreference) { DialogFragment f = DialogPreference.DialogPreferenceCompatDialogFragment.newInstance(preference.getKey()); f.setTargetFragment(this, 0); - assert getFragmentManager() != null; f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); } else { super.onDisplayPreferenceDialog(preference); diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java index c94b3bd6..5adeeb88 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java @@ -192,7 +192,6 @@ public class Condition { public synchronized void evaluate(Context context) { active = evaluation == null || evaluation.isActive(context); - assert evaluation != null; evaluatedPlurals = evaluation.getPluralsCount(); evaluated = true; evaluating = false; @@ -223,40 +222,29 @@ public class Condition { @StringRes private int titleRes; @PluralsRes - private final int titlePluralsRes; + private int titlePluralsRes; private CharSequence title; @StringRes private int summaryRes; @PluralsRes - private final int summaryPluralsRes; + private int summaryPluralsRes; private CharSequence summary; @StringRes - private final int firstActionTextRes; + private int firstActionTextRes; @PluralsRes - private final int firstActionPluralsRes; - private final CharSequence firstActionText; - private final View.OnClickListener firstActionListener; + private int firstActionPluralsRes; + private CharSequence firstActionText; + private View.OnClickListener firstActionListener; @StringRes - private final int secondActionTextRes; + private int secondActionTextRes; @PluralsRes - private final int secondActionPluralsRes; - private final CharSequence secondActionText; - private final View.OnClickListener secondActionListener; - private final Evaluation evaluation; + private int secondActionPluralsRes; + private CharSequence secondActionText; + private View.OnClickListener secondActionListener; + private Evaluation evaluation; - public Builder(int titlePluralsRes, int summaryPluralsRes, int firstActionTextRes, int firstActionPluralsRes, CharSequence firstActionText, View.OnClickListener firstActionListener, int secondActionTextRes, int secondActionPluralsRes, CharSequence secondActionText, View.OnClickListener secondActionListener, Evaluation evaluation) { - this.titlePluralsRes = titlePluralsRes; - this.summaryPluralsRes = summaryPluralsRes; - this.firstActionTextRes = firstActionTextRes; - this.firstActionPluralsRes = firstActionPluralsRes; - this.firstActionText = firstActionText; - this.firstActionListener = firstActionListener; - this.secondActionTextRes = secondActionTextRes; - this.secondActionPluralsRes = secondActionPluralsRes; - this.secondActionText = secondActionText; - this.secondActionListener = secondActionListener; - this.evaluation = evaluation; + public Builder() { } public Builder icon(Drawable val) { @@ -279,6 +267,11 @@ public class Condition { return this; } + public Builder titlePlurals(@PluralsRes int val) { + titlePluralsRes = val; + return this; + } + public Builder summary(CharSequence val) { summary = val; return this; @@ -289,6 +282,52 @@ public class Condition { return this; } + public Builder summaryPlurals(@PluralsRes int val) { + summaryPluralsRes = val; + return this; + } + + public Builder firstAction(CharSequence text, View.OnClickListener listener) { + firstActionText = text; + firstActionListener = listener; + return this; + } + + public Builder firstAction(@StringRes int val, View.OnClickListener listener) { + firstActionTextRes = val; + firstActionListener = listener; + return this; + } + + public Builder firstActionPlurals(@PluralsRes int val, View.OnClickListener listener) { + firstActionPluralsRes = val; + firstActionListener = listener; + return this; + } + + public Builder secondAction(CharSequence text, View.OnClickListener listener) { + secondActionText = text; + secondActionListener = listener; + return this; + } + + public Builder secondAction(@StringRes int val, View.OnClickListener listener) { + secondActionTextRes = val; + secondActionListener = listener; + return this; + } + + public Builder secondActionPlurals(@PluralsRes int val, View.OnClickListener listener) { + secondActionPluralsRes = val; + secondActionListener = listener; + return this; + } + + public Builder evaluation(Evaluation evaluation) { + this.evaluation = evaluation; + return this; + } + public Condition build() { return new Condition(this); } diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java index b5335089..af07a691 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java @@ -24,7 +24,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; -import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import androidx.preference.Preference; import androidx.preference.PreferenceDialogFragmentCompat; @@ -60,21 +59,22 @@ public class DialogPreference extends androidx.preference.DialogPreference imple * Called when the dialog is dismissed and should be used to save data to * the {@link SharedPreferences}. * + * @param positiveResult Whether the positive button was clicked (true), or + * the negative button was clicked or the dialog was canceled (false). */ - protected void onDialogClosed() { + protected void onDialogClosed(boolean positiveResult) { } @Override - public boolean onPreferenceDisplayDialog(@NonNull PreferenceFragmentCompat caller, @NonNull Preference pref) { + public boolean onPreferenceDisplayDialog(PreferenceFragmentCompat caller, Preference pref) { DialogPreferenceCompatDialogFragment fragment = new DialogPreferenceCompatDialogFragment(); fragment.setTargetFragment(caller, 0); - assert caller.getFragmentManager() != null; fragment.show(caller.getFragmentManager(), DIALOG_FRAGMENT_TAG); return true; } @Override - public void onBindViewHolder(@NonNull PreferenceViewHolder view) { + public void onBindViewHolder(PreferenceViewHolder view) { super.onBindViewHolder(view); ViewGroup.LayoutParams layoutParams = view.findViewById(R.id.icon_frame).getLayoutParams(); @@ -88,7 +88,7 @@ public class DialogPreference extends androidx.preference.DialogPreference imple public static class DialogPreferenceCompatDialogFragment extends PreferenceDialogFragmentCompat { @Override - protected View onCreateDialogView(@NonNull Context context) { + protected View onCreateDialogView(Context context) { if (getPreference() instanceof DialogPreference) { View view = ((DialogPreference) getPreference()).onCreateDialogView(); if (view != null) return view; @@ -99,7 +99,7 @@ public class DialogPreference extends androidx.preference.DialogPreference imple @Override public void onDialogClosed(boolean positiveResult) { if (getPreference() instanceof DialogPreference) { - ((DialogPreference) getPreference()).onDialogClosed(); + ((DialogPreference) getPreference()).onDialogClosed(positiveResult); } } diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/LongTextPreference.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/LongTextPreference.java new file mode 100644 index 00000000..4c6edb8b --- /dev/null +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/LongTextPreference.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 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. + */ + +package org.microg.tools.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +public class LongTextPreference extends Preference { + + public LongTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public LongTextPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public LongTextPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LongTextPreference(Context context) { + super(context); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + TextView view = (TextView) holder.findViewById(android.R.id.summary); + if (view != null) { + view.setMaxLines(Integer.MAX_VALUE); + } + } +} diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/RadioButtonPreference.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/RadioButtonPreference.java new file mode 100644 index 00000000..e45d3812 --- /dev/null +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/RadioButtonPreference.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 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. + */ + +package org.microg.tools.ui; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; + +import androidx.core.content.res.TypedArrayUtils; +import androidx.preference.CheckBoxPreference; + +public class RadioButtonPreference extends CheckBoxPreference { + + public RadioButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public RadioButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setWidgetLayoutResource(R.layout.preference_widget_radiobutton); + } + + @SuppressLint("RestrictedApi") + public RadioButtonPreference(Context context, AttributeSet attrs) { + this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.checkBoxPreferenceStyle, + android.R.attr.checkBoxPreferenceStyle)); + } + + public RadioButtonPreference(Context context) { + this(context, null); + } +} diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java index 0a30f683..b9144222 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java @@ -116,12 +116,39 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC mSwitch.setChecked(checked); } + public void setCheckedInternal(boolean checked) { + setTextViewLabel(checked); + mSwitch.setCheckedInternal(checked); + } + + public boolean isChecked() { + return mSwitch.isChecked(); + } + public void setEnabled(boolean enabled) { super.setEnabled(enabled); mTextView.setEnabled(enabled); mSwitch.setEnabled(enabled); } + public final ToggleSwitch getSwitch() { + return mSwitch; + } + + public void show() { + if (!isShowing()) { + setVisibility(View.VISIBLE); + mSwitch.setOnCheckedChangeListener(this); + } + } + + public void hide() { + if (isShowing()) { + setVisibility(View.GONE); + mSwitch.setOnCheckedChangeListener(null); + } + } + public boolean isShowing() { return (getVisibility() == View.VISIBLE); } @@ -151,6 +178,13 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC mSwitchChangeListeners.add(listener); } + public void removeOnSwitchChangeListener(OnSwitchChangeListener listener) { + if (!mSwitchChangeListeners.contains(listener)) { + throw new IllegalStateException("Cannot remove OnSwitchChangeListener"); + } + mSwitchChangeListeners.remove(listener); + } + static class SavedState extends BaseSavedState { boolean checked; boolean visible; diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java index 3a3a7bb3..6530319e 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java @@ -44,6 +44,10 @@ public class ToggleSwitch extends SwitchMaterial { super(context, attrs, defStyleAttr); } + public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) { + mOnBeforeListener = listener; + } + @Override public void setChecked(boolean checked) { if (mOnBeforeListener != null diff --git a/play-services-core/microg-ui-tools/src/main/res/layout/switch_bar.xml b/play-services-core/microg-ui-tools/src/main/res/layout/switch_bar.xml index dfebf8c9..c930c886 100644 --- a/play-services-core/microg-ui-tools/src/main/res/layout/switch_bar.xml +++ b/play-services-core/microg-ui-tools/src/main/res/layout/switch_bar.xml @@ -35,7 +35,6 @@ android:id="@+id/switch_widget" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@null" android:layout_gravity="center_vertical" android:layout_marginEnd="16dp" android:layout_marginRight="16dp"/> diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 4b4c1489..7cf8a16f 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -18,22 +18,11 @@ xmlns:tools="http://schemas.android.com/tools"> - >>>>>> caf7201f (Declare com.mgoogle.android.providers.gsf.permission.READ_GSERVICES permission https://github.com/microg/GmsCore/commit/833e2940f283eeda5eca23b3af898dbfbab24d57) android:name="${basePackageName}.android.c2dm.permission.RECEIVE" ->>>>>>> 25329625 (Set basePackageName) android:label="@string/perm_c2dm_receive_label" android:permissionGroup="android.permission-group.NETWORK" android:protectionLevel="normal" /> @@ -63,8 +52,7 @@ android:protectionLevel="dangerous" /> - + @@ -74,6 +62,7 @@ + @@ -127,15 +116,13 @@ - + - + @@ -163,8 +150,7 @@ + android:process=":persistent"> @@ -175,8 +161,7 @@ + android:process=":persistent"> @@ -188,8 +173,7 @@ + android:process=":persistent"> @@ -201,8 +185,7 @@ + android:process=":persistent"> @@ -225,8 +208,7 @@ + android:process=":persistent"> @@ -238,8 +220,7 @@ - + @@ -297,30 +278,26 @@ + android:process=":ui"> - + - + - + @@ -334,8 +311,7 @@ android:process=":ui" android:roundIcon="@mipmap/ic_launcher" android:taskAffinity="org.microg.gms.settings" - android:theme="@style/AppTheme" - android:exported="true"> + android:theme="@style/AppTheme"> @@ -365,8 +341,7 @@ android:process=":ui" android:targetActivity="org.microg.gms.ui.SettingsActivity" android:taskAffinity="org.microg.gms.settings" - android:theme="@style/AppTheme" - android:exported="true"> + android:theme="@style/AppTheme"> @@ -400,10 +375,6 @@ -======= android:taskAffinity="org.microg.gms.settings" /> ->>>>>>> 902d5e3d (Add basic Google account settings https://github.com/microg/GmsCore/commit/dcf5f1fecb9add7e640a6bb780cb0c752e5c025e) @@ -439,16 +409,14 @@ - + - + diff --git a/play-services-core/src/main/java/com/google/android/gms/chimera/DynamiteModuleInitializer.java b/play-services-core/src/main/java/com/google/android/gms/chimera/DynamiteModuleInitializer.java new file mode 100644 index 00000000..3605e8ac --- /dev/null +++ b/play-services-core/src/main/java/com/google/android/gms/chimera/DynamiteModuleInitializer.java @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.chimera; + +import android.annotation.SuppressLint; +import android.util.Log; +import androidx.annotation.Keep; + +import android.content.Context; + +@Keep +public class DynamiteModuleInitializer { + private static final String TAG = "DynamiteModule"; + + public static void initializeModuleV1(Context context) { + initializeModuleV2(context, "com.google.android.gms".equals(context.getPackageName())); + } + + public static void initializeModuleV2(Context context, boolean withGmsPackage) { + Log.d(TAG, "initializeModuleV2 context: " + context + ", withGmsPackage: " + withGmsPackage); + } +} diff --git a/play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java b/play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java deleted file mode 100644 index 948361f0..00000000 --- a/play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package com.google.android.gms.common; - -import android.content.pm.PackageManager; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import androidx.annotation.Keep; - -import com.google.android.gms.common.internal.CertData; -import com.google.android.gms.common.internal.GoogleCertificatesQuery; -import com.google.android.gms.common.internal.IGoogleCertificatesApi; -import com.google.android.gms.dynamic.IObjectWrapper; -import com.google.android.gms.dynamic.ObjectWrapper; - -import org.microg.gms.common.PackageUtils; - -import java.util.Collections; -import java.util.Set; - -@Keep -public class GoogleCertificatesImpl extends IGoogleCertificatesApi.Stub { - private static final String TAG = "GmsCertImpl"; - private Set googleCertificates = Collections.emptySet(); - private Set googleReleaseCertificates = Collections.emptySet(); - - @Override - public IObjectWrapper getGoogleCertificates() throws RemoteException { - Log.d(TAG, "unimplemented Method: getGoogleCertificates"); - return ObjectWrapper.wrap(googleCertificates.toArray(new IBinder[0])); - } - - @Override - public IObjectWrapper getGoogleReleaseCertificates() throws RemoteException { - Log.d(TAG, "unimplemented Method: getGoogleReleaseCertificates"); - return ObjectWrapper.wrap(googleReleaseCertificates.toArray(new IBinder[0])); - } - - @Override - public boolean isGoogleReleaseSigned(String packageName, IObjectWrapper certData) throws RemoteException { - return PackageUtils.isGooglePackage(packageName, ObjectWrapper.unwrapTyped(certData, byte[].class)); - } - - @Override - public boolean isGoogleSigned(String packageName, IObjectWrapper certData) throws RemoteException { - return PackageUtils.isGooglePackage(packageName, ObjectWrapper.unwrapTyped(certData, byte[].class)); - } - - @Override - public boolean isGoogleOrPlatformSigned(GoogleCertificatesQuery query, IObjectWrapper packageManager) throws RemoteException { - PackageManager pm = ObjectWrapper.unwrapTyped(packageManager, PackageManager.class); - if (query == null || query.getPackageName() == null) { - return false; - } else if (query.getCertData() == null) { - if (pm == null) return false; - return PackageUtils.isGooglePackage(pm, query.getPackageName()); - } else { - return PackageUtils.isGooglePackage(query.getPackageName(), query.getCertData().getBytes()); - } - } -} 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 1532a3ed..9274f439 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 @@ -55,12 +55,7 @@ public class AccountContentProvider extends ContentProvider { @Nullable @Override public Bundle call(String method, String arg, Bundle extras) { -<<<<<<< HEAD - String suggestedPackageName; - suggestedPackageName = getCallingPackage(); -======= String suggestedPackageName = getCallingPackage(); ->>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) String packageName = PackageUtils.getAndCheckCallingPackage(getContext(), suggestedPackageName); Log.d(TAG, "Call " + method + " from " + packageName + " with arg " + arg); if (!PackageUtils.callerHasExtendedAccess(Objects.requireNonNull(getContext()))) { @@ -77,11 +72,7 @@ public class AccountContentProvider extends ContentProvider { if (arg != null && (arg.equals(DEFAULT_ACCOUNT_TYPE) || arg.startsWith(DEFAULT_ACCOUNT_TYPE + "."))) { AccountManager am = AccountManager.get(getContext()); accounts = am.getAccountsByTypeForPackage(arg, packageName); -<<<<<<< HEAD - if (accounts == null || accounts.length == 0) { -======= if (accounts.length == 0) { ->>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) accounts = am.getAccountsByType(arg); } if (SDK_INT >= 26 && arg.equals(DEFAULT_ACCOUNT_TYPE)) { 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 5093bbe6..7f57a370 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 @@ -16,23 +16,24 @@ package org.microg.gms.auth; -import static android.accounts.AccountManager.KEY_ACCOUNTS; -import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; -import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE; -import static android.accounts.AccountManager.KEY_AUTHTOKEN; -import static android.accounts.AccountManager.KEY_CALLER_PID; - import android.accounts.Account; import android.accounts.AccountManager; import android.annotation.SuppressLint; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Parcel; import android.os.RemoteException; +import android.util.Base64; import android.util.Log; +import androidx.core.app.NotificationCompat; + import com.google.android.auth.IAuthManagerService; +import com.mgoogle.android.gms.R; import com.google.android.gms.auth.AccountChangeEventsRequest; import com.google.android.gms.auth.AccountChangeEventsResponse; import com.google.android.gms.auth.GetHubTokenInternalResponse; @@ -45,15 +46,13 @@ 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; -<<<<<<< HEAD -======= import static android.accounts.AccountManager.*; import static android.os.Build.VERSION.SDK_INT; ->>>>>>> 6f98f5aa (Asorted fixes https://github.com/microg/GmsCore/commit/80c1d9cf61baeec0692ec9efd3233305cda3c016) public class AuthManagerServiceImpl extends IAuthManagerService.Stub { private static final String TAG = "GmsAuthManagerSvc"; @@ -72,6 +71,7 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { public static final String KEY_DELEGATEE_USER_ID = "delegatee_user_id"; public static final String KEY_ERROR = "Error"; + public static final String KEY_USER_RECOVERY_INTENT = "userRecoveryIntent"; private final Context context; @@ -178,10 +178,6 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override -<<<<<<< HEAD - public Bundle requestGoogleAccountsAccess(String packageName) { - Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")"); -======= public Bundle requestGoogleAccountsAccess(String packageName) throws RemoteException { PackageUtils.assertExtendedAccess(context); if (SDK_INT >= 26) { @@ -194,22 +190,17 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } else { Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")"); } ->>>>>>> 6f98f5aa (Asorted fixes https://github.com/microg/GmsCore/commit/80c1d9cf61baeec0692ec9efd3233305cda3c016) return null; } @Override -<<<<<<< HEAD - public int hasCapabilities(HasCababilitiesRequest request) { -======= public int hasCapabilities(HasCapabilitiesRequest request) throws RemoteException { Log.w(TAG, "Not implemented: hasCapabilities(" + request.account + ", " + Arrays.toString(request.capabilities) + ")"); ->>>>>>> d04e2718 (Auth: Forward UID/PID from IAuthManagerService.getTokenWithAccount https://github.com/microg/GmsCore/commit/4a7bac35f8eeb1bb3cb1607dc03b439b819abe40) return 1; } @Override - public GetHubTokenInternalResponse getHubToken(GetHubTokenRequest request, Bundle extras) { + public GetHubTokenInternalResponse getHubToken(GetHubTokenRequest request, Bundle extras) throws RemoteException { Log.w(TAG, "Not implemented: getHubToken()"); return null; } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java index b4bea3f8..de9832e8 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/login/AssistantActivity.java @@ -45,7 +45,7 @@ public abstract class AssistantActivity extends Activity { @SuppressLint("WrongViewCast") private void formatTitle() { if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - double widthPixels = getResources().getDisplayMetrics().widthPixels; + double widthPixels = (double) (getResources().getDisplayMetrics().widthPixels); findViewById(R.id.title_container).getLayoutParams().height = (int) (dpToPx(TITLE_MIN_HEIGHT) + (TITLE_WIDTH_FACTOR * widthPixels)); } else { 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 af9ce886..5d836001 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 @@ -161,8 +161,10 @@ public class LoginActivity extends AssistantActivity { super.onHuaweiButtonClicked(); state++; if (state == 1) { - hideLauncherIcon(this, false); - UtilsKt.hideIcon(this, false); + if (SDK_INT >= 23) { + hideLauncherIcon(this, false); + UtilsKt.hideIcon(this, false); + } if (!isSpoofingEnabled(this)) { LastCheckinInfo.clear(this); setSpoofingEnabled(this, true); @@ -199,12 +201,22 @@ public class LoginActivity extends AssistantActivity { authContent.addView(loading); setMessage(R.string.auth_connecting); CookieManager.getInstance().setAcceptCookie(true); - CookieManager.getInstance().removeAllCookies(value -> start()); + if (SDK_INT >= 21) { + CookieManager.getInstance().removeAllCookies(value -> start()); + } else { + //noinspection deprecation + CookieManager.getInstance().removeAllCookie(); + start(); + } } private static WebView createWebView(Context context) { WebView webView = new WebView(context); - webView.setVisibility(INVISIBLE); + if (SDK_INT < 21) { + webView.setVisibility(VISIBLE); + } else { + webView.setVisibility(INVISIBLE); + } webView.setLayoutParams(new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); webView.setBackgroundColor(Color.TRANSPARENT); diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java index 84054bd2..df7c245b 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java @@ -80,8 +80,7 @@ public class CheckinClient { DeviceIdentifier deviceIdent, PhoneInfo phoneInfo, LastCheckinInfo checkinInfo, Locale locale, List accounts, Boolean brandSpoof) { - CheckinRequest.Builder builder; - builder = new CheckinRequest.Builder() + CheckinRequest.Builder builder = new CheckinRequest.Builder() .accountCookie(new ArrayList<>()) .androidId(checkinInfo.getAndroidId()) .checkin(new CheckinRequest.Checkin.Builder() diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java index 355800d1..dc67b444 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java @@ -71,7 +71,8 @@ public class CheckinManager { ContentResolver resolver = context.getContentResolver(); for (CheckinResponse.GservicesSetting setting : response.setting) { - GServices.setString(resolver, setting.name.utf8(), setting.value_.utf8()); + if (setting.name != null && setting.value_ != null) + GServices.setString(resolver, setting.name.utf8(), setting.value_.utf8()); } return info; 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 c8fd30b0..f4766b4a 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 @@ -72,6 +72,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.Messenger; import android.os.Parcelable; import android.os.PowerManager; import android.os.SystemClock; @@ -199,7 +200,7 @@ public class McsService extends Service implements Handler.Callback { } private static void logd(Context context, String msg) { - if (context == null || Objects.requireNonNull(GcmPrefs.get(context)).isGcmLogEnabled()) Log.d(TAG, msg); + if (context == null || GcmPrefs.get(context).isGcmLogEnabled()) Log.d(TAG, msg); } @Override @@ -231,7 +232,6 @@ public class McsService extends Service implements Handler.Callback { deviceIdleController = Class.forName("android.os.IDeviceIdleController$Stub") .getMethod("asInterface", IBinder.class).invoke(null, binder); getUserIdMethod = UserHandle.class.getMethod("getUserId", int.class); - assert deviceIdleController != null; addPowerSaveTempWhitelistAppMethod = deviceIdleController.getClass() .getMethod("addPowerSaveTempWhitelistApp", String.class, long.class, int.class, String.class); } @@ -274,7 +274,7 @@ public class McsService extends Service implements Handler.Callback { return false; } // consider connection to be dead if we did not receive an ack within 90s to our ping - int heartbeatMs = Objects.requireNonNull(GcmPrefs.get(context)).getHeartbeatMsFor(activeNetworkPref); + int heartbeatMs = GcmPrefs.get(context).getHeartbeatMsFor(activeNetworkPref); // if disabled for active network, heartbeatMs will be -1 if (heartbeatMs < 0) { closeAll(); @@ -284,7 +284,7 @@ public class McsService extends Service implements Handler.Callback { long timeSinceLastPing = SystemClock.elapsedRealtime() - lastHeartbeatPingElapsedRealtime; if (noAckReceived && timeSinceLastPing > HEARTBEAT_ACK_AFTER_PING_TIMEOUT_MS) { logd(null, "No heartbeat for " + timeSinceLastPing / 1000 + "s, connection assumed to be dead after 90s"); - Objects.requireNonNull(GcmPrefs.get(context)).learnTimeout(context, activeNetworkPref); + GcmPrefs.get(context).learnTimeout(context, activeNetworkPref); return false; } } @@ -307,15 +307,11 @@ public class McsService extends Service implements Handler.Callback { public void scheduleHeartbeat(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); - int heartbeatMs = Objects.requireNonNull(GcmPrefs.get(this)).getHeartbeatMsFor(activeNetworkPref); + int heartbeatMs = GcmPrefs.get(this).getHeartbeatMsFor(activeNetworkPref); if (heartbeatMs < 0) { closeAll(); } logd(context, "Scheduling heartbeat in " + heartbeatMs / 1000 + " seconds..."); -<<<<<<< HEAD - // This is supposed to work even when running in idle and without battery optimization disabled -======= ->>>>>>> b20726b4 (Stop importing android.os.Build https://github.com/microg/GmsCore/commit/56b468ecbb450844c3e41a2b7d85ff979d98734b) alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); } @@ -362,6 +358,7 @@ public class McsService extends Service implements Handler.Callback { String messageId = intent.getStringExtra(EXTRA_MESSAGE_ID); String collapseKey = intent.getStringExtra(EXTRA_COLLAPSE_KEY); + Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); intent.removeExtra(EXTRA_MESSENGER); Parcelable app = intent.getParcelableExtra(EXTRA_APP); @@ -379,8 +376,16 @@ public class McsService extends Service implements Handler.Callback { } intent.removeExtra(EXTRA_APP); + int ttl; try { - intent.hasExtra(EXTRA_TTL); + if (intent.hasExtra(EXTRA_TTL)) { + ttl = Integer.parseInt(intent.getStringExtra(EXTRA_TTL)); + if (ttl < 0 || ttl > maxTtl) { + ttl = maxTtl; + } + } else { + ttl = maxTtl; + } } catch (NumberFormatException e) { // TODO: error TtlUnsupported Log.w(TAG, e); @@ -446,7 +451,7 @@ public class McsService extends Service implements Handler.Callback { } } - private void connect(int port) { + private boolean connect(int port) { try { wasTornDown = false; @@ -469,8 +474,10 @@ public class McsService extends Service implements Handler.Callback { Log.w(TAG, "Exception while connecting to " + SERVICE_HOST + ":" + port, e); rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e)); closeAll(); + return false; } + return true; } private synchronized void connect() { @@ -478,8 +485,8 @@ public class McsService extends Service implements Handler.Callback { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); - activeNetworkPref = Objects.requireNonNull(GcmPrefs.get(this)).getNetworkPrefForInfo(activeNetworkInfo); - if (!Objects.requireNonNull(GcmPrefs.get(this)).isEnabledFor(activeNetworkInfo)) { + activeNetworkPref = GcmPrefs.get(this).getNetworkPrefForInfo(activeNetworkInfo); + if (!GcmPrefs.get(this).isEnabledFor(activeNetworkInfo)) { if (activeNetworkInfo != null) { logd(this, "Don't connect, because disabled for " + activeNetworkInfo.getTypeName()); } else { @@ -539,14 +546,13 @@ public class McsService extends Service implements Handler.Callback { } private void handleHeartbeatAck(HeartbeatAck ack) { - Objects.requireNonNull(GcmPrefs.get(this)).learnReached(this, activeNetworkPref, SystemClock.elapsedRealtime() - lastIncomingNetworkRealtime); + GcmPrefs.get(this).learnReached(this, activeNetworkPref, SystemClock.elapsedRealtime() - lastIncomingNetworkRealtime); lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); wakeLock.release(); } private LoginRequest buildLoginRequest() { LastCheckinInfo info = LastCheckinInfo.read(this); - assert info != null; return new LoginRequest.Builder() .adaptive_heartbeat(false) .auth_service(LoginRequest.AuthService.ANDROID_ID) @@ -559,7 +565,7 @@ public class McsService extends Service implements Handler.Callback { .user(Long.toString(info.getAndroidId())) .use_rmq2(true) .setting(Collections.singletonList(new Setting.Builder().name("new_vc").value_("1").build())) - .received_persistent_id(Objects.requireNonNull(GcmPrefs.get(this)).getLastPersistedIds()) + .received_persistent_id(GcmPrefs.get(this).getLastPersistedIds()) .build(); } @@ -606,7 +612,7 @@ public class McsService extends Service implements Handler.Callback { sendOrderedBroadcast(intent, null); } else { List infos = getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER); - if (infos == null || infos.isEmpty()) { + if (infos.isEmpty()) { logd(this, "No target for message, wut?"); } else { for (ResolveInfo resolveInfo : infos) { @@ -696,7 +702,7 @@ public class McsService extends Service implements Handler.Callback { if (msg.obj instanceof SocketException) { SocketException e = (SocketException) msg.obj; if ("Connection reset".equals(e.getMessage())) { - Objects.requireNonNull(GcmPrefs.get(this)).learnTimeout(this, activeNetworkPref); + GcmPrefs.get(this).learnTimeout(this, activeNetworkPref); } } rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, msg.obj)); diff --git a/play-services-core/src/main/java/org/microg/gms/ui/LegacyAccountSettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/LegacyAccountSettingsActivity.java index e4d5d312..fc015f5e 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/LegacyAccountSettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/LegacyAccountSettingsActivity.java @@ -45,7 +45,7 @@ public class LegacyAccountSettingsActivity extends AbstractSettingsActivity { public static class AccountSettingsFragment extends ResourceSettingsFragment { public AccountSettingsFragment() { - preferencesResource = R.xml.preferences_account; + preferencesResource = R.xml.preferences_google_more; } @Override diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java index d706f213..f0777081 100755 --- a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java @@ -14,13 +14,11 @@ import androidx.navigation.ui.NavigationUI; import com.google.android.material.color.DynamicColors; import com.mgoogle.android.gms.R; -import java.util.Objects; - public class SettingsActivity extends AppCompatActivity { private AppBarConfiguration appBarConfiguration; private NavController getNavController() { - return ((NavHostFragment) Objects.requireNonNull(getSupportFragmentManager().findFragmentById(R.id.navhost))).getNavController(); + return ((NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.navhost)).getNavController(); } @Override diff --git a/play-services-core/src/main/kotlin/com/google/android/gms/common/GoogleCertificatesImpl.kt b/play-services-core/src/main/kotlin/com/google/android/gms/common/GoogleCertificatesImpl.kt new file mode 100644 index 00000000..e1fb7a21 --- /dev/null +++ b/play-services-core/src/main/kotlin/com/google/android/gms/common/GoogleCertificatesImpl.kt @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package com.google.android.gms.common + +import android.content.pm.PackageManager +import android.os.IBinder +import android.os.Parcel +import android.util.Log +import androidx.annotation.Keep +import com.google.android.gms.common.internal.CertData +import com.google.android.gms.common.internal.IGoogleCertificatesApi +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper +import org.microg.gms.common.PackageUtils +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "GoogleCertificates" + +@Keep +class GoogleCertificatesImpl : IGoogleCertificatesApi.Stub() { + private val googleCertificates = emptySet() + private val googleReleaseCertificates = emptySet() + + override fun getGoogleCertificates(): IObjectWrapper { + Log.d(TAG, "unimplemented Method: getGoogleCertificates") + return ObjectWrapper.wrap(googleCertificates.toTypedArray()) + } + + override fun getGoogleReleaseCertificates(): IObjectWrapper { + Log.d(TAG, "unimplemented Method: getGoogleReleaseCertificates") + return ObjectWrapper.wrap(googleReleaseCertificates.toTypedArray()) + } + + override fun isGoogleReleaseSigned(packageName: String, certData: IObjectWrapper): Boolean { + return PackageUtils.isGooglePackage(packageName, ObjectWrapper.unwrapTyped(certData, ByteArray::class.java)) + } + + override fun isGoogleSigned(packageName: String, certData: IObjectWrapper): Boolean { + return PackageUtils.isGooglePackage(packageName, ObjectWrapper.unwrapTyped(certData, ByteArray::class.java)) + } + + override fun isGoogleOrPlatformSigned(query: GoogleCertificatesQuery, packageManager: IObjectWrapper): Boolean { + val pm = ObjectWrapper.unwrapTyped(packageManager, PackageManager::class.java) + return if (query == null || query.callingPackage == null) { + false + } else if (query.getCertData() == null) { + if (pm == null) false else PackageUtils.isGooglePackage(pm, query.callingPackage) + } else { + PackageUtils.isGooglePackage(query.callingPackage, query.getCertData().bytes) + } + } + + override fun isPackageGoogleOrPlatformSigned(query: GoogleCertificatesLookupQuery): GoogleCertificatesLookupResponse { + return certificateLookup(query, true) + } + + override fun isPackageGoogleOrPlatformSignedAvailable(): Boolean { + return true + } + + override fun queryPackageSigned(query: GoogleCertificatesLookupQuery): GoogleCertificatesLookupResponse { + if (!isFineGrainedPackageVerificationAvailable) throw IllegalStateException("API unavailable") + return certificateLookup(query, false) + } + + override fun isFineGrainedPackageVerificationAvailable(): Boolean { + return true + } + + private fun certificateLookup(query: GoogleCertificatesLookupQuery, allowPlatform: Boolean): GoogleCertificatesLookupResponse { + val context = query.context + ?: return GoogleCertificatesLookupResponse(false, "context is null", 5, 1) + val packageManager = context.packageManager + ?: return GoogleCertificatesLookupResponse(false, "context has no package manager", 5, 1) + val callingPackage = query.callingPackage + ?: return GoogleCertificatesLookupResponse(false, "callingPackage is null", 5, 1) + val signatureDigest = PackageUtils.firstSignatureDigest(packageManager, callingPackage) + ?: return GoogleCertificatesLookupResponse(false, "callingPackage not found", 4, 1) + return if (PackageUtils.isGooglePackage(callingPackage, signatureDigest)) { + GoogleCertificatesLookupResponse(true, null, 1, 3) + } else { + GoogleCertificatesLookupResponse(false, "not allowed", 2, 1) + } + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt index 5df2669d..ba5d2b2e 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt @@ -7,20 +7,16 @@ import org.microg.gms.settings.SettingsContract.Auth object AuthPrefs { @JvmStatic - fun isTrustGooglePermitted(context: Context): Boolean? { - return Auth.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, arrayOf(Auth.TRUST_GOOGLE)) { c -> - c.getInt(0) != 0 - } + fun isTrustGooglePermitted(context: Context): Boolean { + return SettingsContract.getSettings(context, Auth.getContentUri(context), arrayOf(Auth.TRUST_GOOGLE)) { c -> + c.getInt(0) != 0 } } @JvmStatic - fun isAuthVisible(context: Context): Boolean? { - return Auth.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, arrayOf(Auth.VISIBLE)) { c -> - c.getInt(0) != 0 - } + fun isAuthVisible(context: Context): Boolean { + return SettingsContract.getSettings(context, Auth.getContentUri(context), arrayOf(Auth.VISIBLE)) { c -> + c.getInt(0) != 0 } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt index 3dee8489..c99b3cc3 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt @@ -7,6 +7,7 @@ package org.microg.gms.auth.appcert import android.content.Context import android.database.Cursor +import android.os.SystemClock import android.util.Base64 import android.util.Log import com.android.volley.NetworkResponse @@ -62,7 +63,7 @@ class AppCertManager(private val context: Context) { Log.w(TAG, "DeviceKeys for app certifications are experimental") deviceKeyCacheTime = currentTime val lastCheckinInfo = LastCheckinInfo.read(context) - val androidId = lastCheckinInfo?.androidId + val androidId = lastCheckinInfo.androidId val sessionId = Random.nextLong() val token = completeRegisterRequest(context, GcmDatabase(context), RegisterRequest().build(context) .checkin(lastCheckinInfo) @@ -75,7 +76,7 @@ class AppCertManager(private val context: Context) { .extraParam("scope", REGISTER_SCOPE)) .getString(GcmConstants.EXTRA_REGISTRATION_ID) val request = DeviceKeyRequest( - androidId = lastCheckinInfo?.androidId, + androidId = lastCheckinInfo.androidId, sessionId = sessionId, versionInfo = DeviceKeyRequest.VersionInfo(Build.VERSION.SDK_INT, BuildConfig.VERSION_CODE), token = token @@ -111,10 +112,10 @@ class AppCertManager(private val context: Context) { override fun getHeaders(): Map { return mapOf( - "User-Agent" to "GoogleAuth/1.4 (${Build.DEVICE} ${Build.ID}); gzip", - "content-type" to "application/octet-stream", - "app" to "com.google.android.gms", - "device" to androidId!!.toString(16) + "User-Agent" to "GoogleAuth/1.4 (${Build.DEVICE} ${Build.ID}); gzip", + "content-type" to "application/octet-stream", + "app" to "com.google.android.gms", + "device" to androidId.toString(16) ) } }) @@ -153,14 +154,7 @@ class AppCertManager(private val context: Context) { ) } else { Log.d(TAG, "Using fallback spatula header based on Android ID") -<<<<<<< HEAD - val androidId = CheckIn.getContentUri(context)?.let { - getSettings(context, - it, arrayOf(CheckIn.ANDROID_ID, CheckIn.SECURITY_TOKEN)) { cursor: Cursor -> cursor.getLong(0) } - } -======= val androidId = getSettings(context, CheckIn.getContentUri(context), arrayOf(CheckIn.ANDROID_ID)) { cursor: Cursor -> cursor.getLong(0) } ->>>>>>> dc5ec8f7 (AppCert: Update for latest API https://github.com/microg/GmsCore/commit/3a42d956abb017e35d30204c556afcc29cb8a636) SpatulaHeaderProto( packageInfo = SpatulaHeaderProto.PackageInfo(packageName, packageCertificateHash), deviceId = androidId diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt index f782fc7e..ede33d26 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt @@ -32,7 +32,7 @@ private const val TAG = "AuthProxyService" class AuthProxyService : BaseService(TAG, GmsService.AUTH_PROXY) { override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) - ?: throw IllegalArgumentException("Missing package name") + ?: throw IllegalArgumentException("Missing package name") val consumerPackageName = request.extras.getString("consumerPkg") if (consumerPackageName != null) PackageUtils.assertExtendedAccess(this) val serviceImpl = AuthServiceImpl(this, lifecycle, consumerPackageName ?: packageName) @@ -59,10 +59,5 @@ class AuthServiceImpl(private val context: Context, private val lifecycle: Lifec override fun getLifecycle(): Lifecycle = lifecycle -<<<<<<< HEAD - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } -} -======= override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } } ->>>>>>> 6f98f5aa (Asorted fixes https://github.com/microg/GmsCore/commit/80c1d9cf61baeec0692ec9efd3233305cda3c016) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt index d14bc61b..e2ab990a 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt @@ -12,40 +12,32 @@ import org.microg.gms.settings.SettingsContract.setSettings object CheckinPrefs { @JvmStatic - fun isEnabled(context: Context): Boolean? { + fun isEnabled(context: Context): Boolean { val projection = arrayOf(CheckIn.ENABLED) - return CheckIn.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, projection) { c -> - c.getInt(0) != 0 - } + return SettingsContract.getSettings(context, CheckIn.getContentUri(context), projection) { c -> + c.getInt(0) != 0 } } @JvmStatic - fun isSpoofingEnabled(context: Context): Boolean? { + fun isSpoofingEnabled(context: Context): Boolean { val projection = arrayOf(CheckIn.BRAND_SPOOF) - return CheckIn.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, projection) { c -> - c.getInt(0) != 0 - } + return SettingsContract.getSettings(context, CheckIn.getContentUri(context), projection) { c -> + c.getInt(0) != 0 } } @JvmStatic fun setSpoofingEnabled(context: Context, enabled: Boolean) { - CheckIn.getContentUri(context)?.let { - setSettings(context, it) { - put(CheckIn.BRAND_SPOOF, enabled) - } + setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.BRAND_SPOOF, enabled) } } @JvmStatic fun hideLauncherIcon(context: Context, enabled: Boolean) { - CheckIn.getContentUri(context)?.let { - setSettings(context, it) { - put(CheckIn.HIDE_LAUNCHER_ICON, enabled) - } + setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.HIDE_LAUNCHER_ICON, enabled) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt index ea607093..ab2dc042 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/LastCheckinInfo.kt @@ -39,7 +39,7 @@ data class LastCheckinInfo( companion object { @JvmStatic - fun read(context: Context): LastCheckinInfo? { + fun read(context: Context): LastCheckinInfo { val projection = arrayOf( CheckIn.ANDROID_ID, CheckIn.DIGEST, @@ -48,43 +48,35 @@ data class LastCheckinInfo( CheckIn.VERSION_INFO, CheckIn.DEVICE_DATA_VERSION_INFO, ) - return CheckIn.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, projection) { c -> - LastCheckinInfo( - androidId = c.getLong(0), - digest = c.getString(1), - lastCheckin = c.getLong(2), - securityToken = c.getLong(3), - versionInfo = c.getString(4), - deviceDataVersionInfo = c.getString(5), - ) - } + return SettingsContract.getSettings(context, CheckIn.getContentUri(context), projection) { c -> + LastCheckinInfo( + androidId = c.getLong(0), + digest = c.getString(1), + lastCheckin = c.getLong(2), + securityToken = c.getLong(3), + versionInfo = c.getString(4), + deviceDataVersionInfo = c.getString(5), + ) } } @JvmStatic - fun clear(context: Context) = - CheckIn.getContentUri(context)?.let { - SettingsContract.setSettings(context, it) { - put(CheckIn.ANDROID_ID, 0L) - put(CheckIn.DIGEST, CheckIn.INITIAL_DIGEST) - put(CheckIn.LAST_CHECK_IN, 0L) - put(CheckIn.SECURITY_TOKEN, 0L) - put(CheckIn.VERSION_INFO, "") - put(CheckIn.DEVICE_DATA_VERSION_INFO, "") - } - } + fun clear(context: Context) = SettingsContract.setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.ANDROID_ID, 0L) + put(CheckIn.DIGEST, CheckIn.INITIAL_DIGEST) + put(CheckIn.LAST_CHECK_IN, 0L) + put(CheckIn.SECURITY_TOKEN, 0L) + put(CheckIn.VERSION_INFO, "") + put(CheckIn.DEVICE_DATA_VERSION_INFO, "") + } } - fun write(context: Context) = - CheckIn.getContentUri(context)?.let { - SettingsContract.setSettings(context, it) { - put(CheckIn.ANDROID_ID, androidId) - put(CheckIn.DIGEST, digest) - put(CheckIn.LAST_CHECK_IN, lastCheckin) - put(CheckIn.SECURITY_TOKEN, securityToken) - put(CheckIn.VERSION_INFO, versionInfo) - put(CheckIn.DEVICE_DATA_VERSION_INFO, deviceDataVersionInfo) - } - } + fun write(context: Context) = SettingsContract.setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.ANDROID_ID, androidId) + put(CheckIn.DIGEST, digest) + put(CheckIn.LAST_CHECK_IN, lastCheckin) + put(CheckIn.SECURITY_TOKEN, securityToken) + put(CheckIn.VERSION_INFO, versionInfo) + put(CheckIn.DEVICE_DATA_VERSION_INFO, deviceDataVersionInfo) + } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt index 7459d4bd..d42c9e03 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/ServiceInfo.kt @@ -20,26 +20,22 @@ data class ServiceConfiguration(val enabled: Boolean) : Serializable suspend fun getCheckinServiceInfo(context: Context): ServiceInfo = withContext(Dispatchers.IO) { val projection = arrayOf(CheckIn.ENABLED, CheckIn.LAST_CHECK_IN, CheckIn.ANDROID_ID) - CheckIn.getContentUri(context)?.let { - getSettings(context, it, projection) { c -> - ServiceInfo( - configuration = ServiceConfiguration(c.getInt(0) != 0), - lastCheckin = c.getLong(1), - androidId = c.getLong(2), - ) - } - }!! + getSettings(context, CheckIn.getContentUri(context), projection) { c -> + ServiceInfo( + configuration = ServiceConfiguration(c.getInt(0) != 0), + lastCheckin = c.getLong(1), + androidId = c.getLong(2), + ) + } } suspend fun setCheckinServiceConfiguration(context: Context, configuration: ServiceConfiguration) = withContext(Dispatchers.IO) { val serviceInfo = getCheckinServiceInfo(context) if (serviceInfo.configuration == configuration) return@withContext // enabled state is not already set, setting it now - CheckIn.getContentUri(context)?.let { - setSettings(context, it) { + setSettings(context, CheckIn.getContentUri(context)) { put(CheckIn.ENABLED, configuration.enabled) } - } if (configuration.enabled) { context.sendOrderedBroadcast(Intent(context, TriggerReceiver::class.java), null) } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt index 7b0046e8..86e7a37d 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt @@ -42,44 +42,39 @@ data class GcmPrefs( private const val MAX_INTERVAL = 15 * 60 * 1000 // 15 minutes @JvmStatic - fun get(context: Context): GcmPrefs? { - return Gcm.getContentUri(context)?.let { - SettingsContract.getSettings(context, it, Gcm.PROJECTION) { c -> - GcmPrefs( - isGcmLogEnabled = c.getInt(0) != 0, - lastPersistedId = c.getString(1), - gcmEnabled = c.getInt(2) != 0, - networkMobile = c.getInt(3), - networkWifi = c.getInt(4), - networkRoaming = c.getInt(5), - networkOther = c.getInt(6), - learntMobileInterval = c.getInt(7), - learntWifiInterval = c.getInt(8), - learntOtherInterval = c.getInt(9), - ) - } + fun get(context: Context): GcmPrefs { + return SettingsContract.getSettings(context, Gcm.getContentUri(context), Gcm.PROJECTION) { c -> + GcmPrefs( + isGcmLogEnabled = c.getInt(0) != 0, + lastPersistedId = c.getString(1), + gcmEnabled = c.getInt(2) != 0, + networkMobile = c.getInt(3), + networkWifi = c.getInt(4), + networkRoaming = c.getInt(5), + networkOther = c.getInt(6), + learntMobileInterval = c.getInt(7), + learntWifiInterval = c.getInt(8), + learntOtherInterval = c.getInt(9), + ) } } fun write(context: Context, config: ServiceConfiguration) { - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.ENABLE_GCM, config.enabled) - put(Gcm.NETWORK_MOBILE, config.mobile) - put(Gcm.NETWORK_WIFI, config.wifi) - put(Gcm.NETWORK_ROAMING, config.roaming) - put(Gcm.NETWORK_OTHER, config.other) - } + val gcmPrefs = get(context) + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.ENABLE_GCM, config.enabled) + put(Gcm.NETWORK_MOBILE, config.mobile) + put(Gcm.NETWORK_WIFI, config.wifi) + put(Gcm.NETWORK_ROAMING, config.roaming) + put(Gcm.NETWORK_OTHER, config.other) } - get(context)?.setEnabled(context, config.enabled) + gcmPrefs.setEnabled(context, config.enabled) } @JvmStatic fun clearLastPersistedId(context: Context) { - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LAST_PERSISTENT_ID, "") - } + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LAST_PERSISTENT_ID, "") } } } @@ -127,25 +122,6 @@ data class GcmPrefs( fun learnTimeout(context: Context, pref: String) { Log.d("GmsGcmPrefs", "learnTimeout: $pref") when (pref) { -<<<<<<< HEAD - PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> Gcm.getContentUri(context)?.let { - setSettings(context, it) { - val newInterval = (learntMobileInterval * 0.95).toInt() - put(Gcm.LEARNT_MOBILE, newInterval) - } - } - PREF_NETWORK_WIFI -> Gcm.getContentUri(context)?.let { - setSettings(context, it) { - val newInterval = (learntMobileInterval * 0.95).toInt() - put(Gcm.LEARNT_WIFI, newInterval) - } - } - else -> Gcm.getContentUri(context)?.let { - setSettings(context, it) { - val newInterval = (learntMobileInterval * 0.95).toInt() - put(Gcm.LEARNT_OTHER, newInterval) - } -======= PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> setSettings(context, Gcm.getContentUri(context)) { val newInterval = (learntMobileInterval * 0.95).toInt() put(Gcm.LEARNT_MOBILE, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) @@ -157,7 +133,6 @@ data class GcmPrefs( else -> setSettings(context, Gcm.getContentUri(context)) { val newInterval = (learntOtherInterval * 0.95).toInt() put(Gcm.LEARNT_OTHER, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) ->>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) } } } @@ -167,46 +142,25 @@ data class GcmPrefs( when (pref) { PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> { if (time > learntMobileInterval / 4 * 3) { -<<<<<<< HEAD - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LEARNT_MOBILE, SettingsProvider.INTERVAL) - } -======= val newInterval = (learntMobileInterval * 1.02).toInt() setSettings(context, Gcm.getContentUri(context)) { put(Gcm.LEARNT_MOBILE, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) ->>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) } } } PREF_NETWORK_WIFI -> { if (time > learntWifiInterval / 4 * 3) { -<<<<<<< HEAD - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LEARNT_WIFI, SettingsProvider.INTERVAL) - } -======= val newInterval = (learntWifiInterval * 1.02).toInt() setSettings(context, Gcm.getContentUri(context)) { put(Gcm.LEARNT_WIFI, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) ->>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) } } } else -> { if (time > learntOtherInterval / 4 * 3) { -<<<<<<< HEAD - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LEARNT_OTHER, SettingsProvider.INTERVAL) - } -======= val newInterval = (learntOtherInterval * 1.02).toInt() setSettings(context, Gcm.getContentUri(context)) { put(Gcm.LEARNT_OTHER, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) ->>>>>>> b0b92c8c (Set max auto heartbeat interval to 15min https://github.com/microg/GmsCore/commit/1e96dd137eae86bbf3be3ca72a6345fa837ad8ff) } } } @@ -220,10 +174,8 @@ data class GcmPrefs( fun extendLastPersistedId(context: Context, id: String) { val newId = if (lastPersistedId.isNullOrEmpty()) id else "$lastPersistedId|$id" - Gcm.getContentUri(context)?.let { - setSettings(context, it) { - put(Gcm.LAST_PERSISTENT_ID, newId) - } + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.LAST_PERSISTENT_ID, newId) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt index 4b7b44bc..1f27e4b6 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt @@ -6,7 +6,6 @@ package org.microg.gms.gcm -import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Context import android.content.Intent @@ -30,32 +29,30 @@ import kotlin.coroutines.suspendCoroutine private const val TAG = "GmsGcmRegister" private suspend fun ensureCheckinIsUpToDate(context: Context) { - if (!CheckinPrefs.isEnabled(context)!!) throw RuntimeException("Checkin disabled") - val lastCheckin = LastCheckinInfo.read(context)?.lastCheckin - if (lastCheckin != null) { - if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) { - val resultData: Bundle = suspendCoroutine { continuation -> - val intent = Intent(context, CheckinService::class.java) - val continued = AtomicBoolean(false) - intent.putExtra(CheckinService.EXTRA_RESULT_RECEIVER, object : ResultReceiver(null) { - override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { - if (continued.compareAndSet(false, true)) continuation.resume(resultData ?: Bundle.EMPTY) - } - }) - ForegroundServiceContext(context).startService(intent) - Handler().postDelayed({ - if (continued.compareAndSet(false, true)) continuation.resume(Bundle.EMPTY) - }, 10000L) - } - if (resultData.getLong(CheckinService.EXTRA_NEW_CHECKIN_TIME, 0L) + lastCheckin == 0L) { - throw RuntimeException("No checkin available") - } + if (!CheckinPrefs.isEnabled(context)) throw RuntimeException("Checkin disabled") + val lastCheckin = LastCheckinInfo.read(context).lastCheckin + if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) { + val resultData: Bundle = suspendCoroutine { continuation -> + val intent = Intent(context, CheckinService::class.java) + val continued = AtomicBoolean(false) + intent.putExtra(CheckinService.EXTRA_RESULT_RECEIVER, object : ResultReceiver(null) { + override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { + if (continued.compareAndSet(false, true)) continuation.resume(resultData ?: Bundle.EMPTY) + } + }) + ForegroundServiceContext(context).startService(intent) + Handler().postDelayed({ + if (continued.compareAndSet(false, true)) continuation.resume(Bundle.EMPTY) + }, 10000L) + } + if (resultData.getLong(CheckinService.EXTRA_NEW_CHECKIN_TIME, 0L) + lastCheckin == 0L) { + throw RuntimeException("No checkin available") } } } private fun ensureAppRegistrationAllowed(context: Context, database: GcmDatabase, packageName: String) { - if (!GcmPrefs.get(context)?.isEnabled!!) throw RuntimeException("GCM disabled") + if (!GcmPrefs.get(context).isEnabled) throw RuntimeException("GCM disabled") val app = database.getApp(packageName) if (app?.allowRegister == false) { throw RuntimeException("Push permission not granted to $packageName") @@ -245,11 +242,10 @@ internal class PushRegisterHandler(private val context: Context, private val dat } private val selfAuthIntent: PendingIntent - @SuppressLint("UnspecifiedImmutableFlag") get() { val intent = Intent() intent.setPackage("com.google.example.invalidpackage") - return PendingIntent.getBroadcast(context, 0, intent, 0) + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } override fun handleMessage(msg: Message) { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt index 1fd34bac..be9c0f67 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ServiceInfo.kt @@ -36,16 +36,14 @@ class ServiceInfoReceiver : BroadcastReceiver() { context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { setPackage(context.packageName) val prefs = GcmPrefs.get(context) - val info = prefs?.let { - ServiceInfo( - configuration = it.toConfiguration(), + val info = ServiceInfo( + configuration = prefs.toConfiguration(), connected = McsService.isConnected(context), startTimestamp = McsService.getStartTimestamp(), learntMobileInterval = prefs.learntMobileInterval, learntWifiInterval = prefs.learntWifiInterval, learntOtherInterval = prefs.learntOtherInterval - ) - } + ) putExtra(EXTRA_SERVICE_INFO, info) }, null) } catch (e: Exception) { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt index ef30f876..de5020c0 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt @@ -13,8 +13,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder class AppIconPreference : Preference { - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super( - context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context) : super(context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt index ef027a84..da861c0b 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt @@ -74,9 +74,9 @@ class PushNotificationPreferencesFragment : PreferenceFragmentCompat() { val statusInfo = getGcmServiceInfo(appContext) pushStatusCategory.isVisible = statusInfo.configuration.enabled pushStatus.summary = if (statusInfo.connected) { - appContext.getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(statusInfo.startTimestamp, System.currentTimeMillis(), 0)) + getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(statusInfo.startTimestamp, System.currentTimeMillis(), 0)) } else { - appContext.getString(R.string.gcm_network_state_disconnected) + getString(R.string.gcm_network_state_disconnected) } } } catch (_: Exception) {} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index 4cb5b9df..86dd492d 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -105,9 +105,6 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_GCM)?.setSummary(R.string.service_status_disabled_short) } -<<<<<<< HEAD - findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPrefs.isEnabled(context) == true) R.string.service_status_enabled_short else R.string.service_status_disabled_short) -======= findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPrefs.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) lifecycleScope.launchWhenResumed { @@ -121,7 +118,6 @@ class SettingsFragment : ResourceSettingsFragment() { else entry.createPreference() } } ->>>>>>> 4018b660 (Build: Change system for optional components https://github.com/microg/GmsCore/commit/dd8c5601f5a521fbf48a2bdcaf4b5ccc13340753) } companion object { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt index 956386a0..a718b876 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/TextPreference.kt @@ -7,6 +7,7 @@ package org.microg.gms.ui import android.content.Context import android.util.AttributeSet +import android.util.TypedValue import android.view.Gravity import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.LinearLayout @@ -15,8 +16,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder class TextPreference : Preference { - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super( - context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context) : super(context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt index 998770e3..b59142d3 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/Utils.kt @@ -15,6 +15,7 @@ import android.os.Bundle import android.provider.Settings import android.util.Log import androidx.annotation.IdRes +import androidx.annotation.RequiresApi import androidx.navigation.NavController import androidx.navigation.navOptions import androidx.navigation.ui.R @@ -41,12 +42,12 @@ fun NavController.navigate(context: Context, @IdRes resId: Int, args: Bundle? = fun Context.hideIcon(hide: Boolean) { packageManager.setComponentEnabledSetting( - ComponentName.createRelative(this, "org.microg.gms.ui.SettingsActivityLauncher"), - when (hide) { - true -> PackageManager.COMPONENT_ENABLED_STATE_DISABLED - false -> PackageManager.COMPONENT_ENABLED_STATE_ENABLED - }, - PackageManager.DONT_KILL_APP + ComponentName.createRelative(this, "org.microg.gms.ui.SettingsActivityLauncher"), + when (hide) { + true -> PackageManager.COMPONENT_ENABLED_STATE_DISABLED + false -> PackageManager.COMPONENT_ENABLED_STATE_ENABLED + }, + PackageManager.DONT_KILL_APP ) } diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index c08c652d..6b0c911c 100755 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -39,7 +39,6 @@ Try again later." This can take a couple of minutes." - read Google service configuration listen to internal status broadcasts read Google service configuration listen to C2DM messages diff --git a/play-services-core/src/main/res/xml/preferences_account.xml b/play-services-core/src/main/res/xml/preferences_account.xml deleted file mode 100755 index 1f24d32e..00000000 --- a/play-services-core/src/main/res/xml/preferences_account.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - -<<<<<<< HEAD - - - - - - - - - - - - - - - -======= - - - - - ->>>>>>> c8d53d6b (Auth: Add option to authenticate without android id https://github.com/microg/GmsCore/commit/8663e52563040bfcc57432173f891108fa693f07) - \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/preferences_google_more.xml b/play-services-core/src/main/res/xml/preferences_google_more.xml index 879f0a18..d2240b0f 100755 --- a/play-services-core/src/main/res/xml/preferences_google_more.xml +++ b/play-services-core/src/main/res/xml/preferences_google_more.xml @@ -17,23 +17,62 @@ - - + + + + + + + + + + + + + + + + + + + - + - diff --git a/play-services-core/src/main/res/xml/preferences_start.xml b/play-services-core/src/main/res/xml/preferences_start.xml index 5cef1aaf..e7372cee 100755 --- a/play-services-core/src/main/res/xml/preferences_start.xml +++ b/play-services-core/src/main/res/xml/preferences_start.xml @@ -22,13 +22,8 @@ android:title="@string/account_manager_title" android:summary="@string/manage_google_account_summary"> -======= - android:targetClass="org.microg.gms.auth.login.LoginActivity" + android:targetClass="org.microg.gms.ui.GoogleMoreFragment$AsActivity" android:targetPackage="@string/var_pref_start_gms_package" /> ->>>>>>> 25329625 (Set basePackageName) - - - + + + \ No newline at end of file diff --git a/play-services-iid-api/src/main/java/com/google/android/gms/iid/MessengerCompat.java b/play-services-iid-api/src/main/java/com/google/android/gms/iid/MessengerCompat.java index 1cc87ae3..7aebf9b4 100644 --- a/play-services-iid-api/src/main/java/com/google/android/gms/iid/MessengerCompat.java +++ b/play-services-iid-api/src/main/java/com/google/android/gms/iid/MessengerCompat.java @@ -26,7 +26,7 @@ import android.os.Parcelable; import android.os.RemoteException; public class MessengerCompat implements Parcelable { - private final Messenger messenger; + private Messenger messenger; private IMessengerCompat messengerCompat; public MessengerCompat(IBinder binder) { @@ -48,7 +48,7 @@ public class MessengerCompat implements Parcelable { } public IBinder getBinder() { - return messenger.getBinder(); + return messenger != null ? messenger.getBinder() : messengerCompat.asBinder(); } @Override @@ -57,7 +57,8 @@ public class MessengerCompat implements Parcelable { } public void send(Message message) throws RemoteException { - messenger.send(message); + if (messenger != null) messenger.send(message); + else messengerCompat.send(message); } @Override diff --git a/play-services-tasks/build.gradle b/play-services-tasks/build.gradle index 204e8d9e..0eb84bbf 100644 --- a/play-services-tasks/build.gradle +++ b/play-services-tasks/build.gradle @@ -22,10 +22,9 @@ android { } compileOptions { - sourceCompatibility = 11 - targetCompatibility = 11 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 } - namespace 'org.microg.gms.tasks' } dependencies { diff --git a/play-services-tasks/src/main/AndroidManifest.xml b/play-services-tasks/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c7b833fe --- /dev/null +++ b/play-services-tasks/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + 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 cbb0a9e6..3cdf1f11 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 @@ -84,11 +84,14 @@ public class TaskCompletionSource { * Completes the Task with the specified exception, unless the Task has already completed. * If the Task has already completed, the call does nothing. * + * @return {@code true} if the exception was set successfully, {@code false} otherwise */ - public void trySetException(Exception e) { + public boolean trySetException(Exception e) { try { setException(e); + return true; } catch (DuplicateTaskCompletionException ignored) { + return false; } } @@ -105,11 +108,14 @@ public class TaskCompletionSource { * Completes the Task with the specified result, unless the Task has already completed. * If the Task has already completed, the call does nothing. * + * @return {@code true} if the result was set successfully, {@code false} otherwise */ - public void trySetResult(TResult result) { + public boolean trySetResult(TResult result) { try { setResult(result); + return true; } catch (DuplicateTaskCompletionException ignored) { + return false; } } } 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 1736b868..8bbc7db6 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 @@ -8,17 +8,85 @@ package com.google.android.gms.tasks; -import org.microg.gms.common.PublicApi; +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. */ @PublicApi +@SuppressWarnings("unchecked") public final class Tasks { + /** + * Blocks until the specified Task is complete. + * + * @return the Task's result + * @throws ExecutionException if the Task fails + * @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) 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"); + } + + /** + * Blocks until the specified Task is complete. + * + * @return the Task's result + * @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) 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 {@link Task} that will be completed with the result of the specified {@code Callable}. *

@@ -60,4 +128,180 @@ public final class Tasks { 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) { + TaskImpl task = new TaskImpl<>(); + task.setException(e); + return task; + } + + /** + * Returns a completed Task with the specified result. + */ + public static Task forResult(TResult result) { + 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) { + 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(); + } } diff --git a/play-services-tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java b/play-services-tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java index 1354fb7e..67b54ea8 100644 --- a/play-services-tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java +++ b/play-services-tasks/src/main/java/org/microg/gms/tasks/TaskImpl.java @@ -17,7 +17,6 @@ import com.google.android.gms.tasks.RuntimeExecutionException; import com.google.android.gms.tasks.SuccessContinuation; import com.google.android.gms.tasks.Task; -import java.util.Objects; import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; @@ -139,7 +138,7 @@ public class TaskImpl extends Task { synchronized (lock) { if (!completed) throw new IllegalStateException("Task is not yet complete"); if (cancelled) throw new CancellationException("Task is canceled"); - if (exceptionType.isInstance(exception)) throw Objects.requireNonNull(exceptionType.cast(exception)); + if (exceptionType.isInstance(exception)) throw exceptionType.cast(exception); if (exception != null) throw new RuntimeExecutionException(exception); return result; } diff --git a/settings.gradle b/settings.gradle index d5f20010..78cd762d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,7 +15,4 @@ include ':play-services-core:microg-ui-tools' // Legacy include ':play-services-core' include ':play-services-base' -include ':play-services-base-core' - -include ':play-services-cast-framework' -include ':play-services-cast' \ No newline at end of file +include ':play-services-base-core' \ No newline at end of file From 519b24613f395af1a260f796297ee36d83b77eed Mon Sep 17 00:00:00 2001 From: Cuynu Date: Fri, 10 May 2024 10:20:12 +0000 Subject: [PATCH 30/63] disable back to YouTube Vanced+ option --- play-services-core/src/main/res/xml/preferences_google_more.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-core/src/main/res/xml/preferences_google_more.xml b/play-services-core/src/main/res/xml/preferences_google_more.xml index d2240b0f..65a3c7e4 100755 --- a/play-services-core/src/main/res/xml/preferences_google_more.xml +++ b/play-services-core/src/main/res/xml/preferences_google_more.xml @@ -75,7 +75,7 @@ android:title="@string/pref_auth_include_android_id_title"/> Date: Fri, 10 May 2024 10:28:10 +0000 Subject: [PATCH 31/63] update repo link to match with current --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 713453d0..90b361b6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Vanced+ MicroG -This fork tweaks MicroG to be usable by applications that require Google authentication such as [YouTube Vanced+](https://gitlab.com/cuynu/ytvancedx). +This fork tweaks MicroG to be usable by applications that require Google authentication such as [YouTube Vanced+](https://gitlab.com/cuynu/vancedx). ## Warning! Vanced+ microG probably does not works in ReVanced/RVX in future as recent changes from ReVanced/RVX to use with they specific MicroG called ReVanced MicroG with different package name ! From 7a389fbe1aa28a6b15ba445676fbbd3ce781d6ab Mon Sep 17 00:00:00 2001 From: Cuynu Date: Fri, 10 May 2024 17:54:40 +0700 Subject: [PATCH 32/63] No commit --- play-services-core/src/main/res/values-vi/strings.xml | 6 +++--- play-services-core/src/main/res/values/strings.xml | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/play-services-core/src/main/res/values-vi/strings.xml b/play-services-core/src/main/res/values-vi/strings.xml index e7cba941..df22a69d 100644 --- a/play-services-core/src/main/res/values-vi/strings.xml +++ b/play-services-core/src/main/res/values-vi/strings.xml @@ -79,6 +79,8 @@ Hãy thử lại sau." Đăng ký thiết bị Đăng ký thiết bị của bạn với các dịch vụ của Google và tạo một mã nhận dạng thiết bị duy nhất. Vanced+ microG tách một ít thông tin nhận dạng không phải tên tài khoản Google của bạn từ dữ liệu đăng ký. ID Android + Xác thực với đăng ký thiết bị + Khi vô hiệu hóa, các yêu cầu xác thực sẽ không được liên kết với đăng ký thiết bị, điều này cho phép các thiết bị trái phép đăng nhập nhưng có thể gây ra những hậu quả về tài khoản. Trạng thái Mặc định Thủ công @@ -98,7 +100,7 @@ Hãy thử lại sau." Cho phép đăng ký Cho phép ứng dụng đăng ký nhận thông báo đẩy. Khởi động ứng dụng từ tin nhắn đẩy - Khởi động ứng dụng ở chế độ nền để nhận các tin nhắn đẩy đến. + Khởi động ứng dụng ở chế độ nền để nhận các thông báo đẩy đến. Trạng thái Ứng dụng sử dụng thông báo đẩy Ứng dụng đã đăng ký @@ -123,8 +125,6 @@ Hãy thử lại sau." Cho phép kết nối tới máy chủ Google Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Giả mạo chữ ký của ứng dụng Vanced+ microG để đánh lừa Google và khắc phục lỗi phát video trên YouTube Vanced+ - Giả mạo chữ ký Vanced+ microG (THỬ NGHIỆM) Ngày dựng Dữ liệu của bạn trong YouTube Xem hoặc xóa hoạt động của bạn trên YouTube và khám phá cách dữ liệu của bạn có thể giúp YouTube và các dịch vụ khác của Google hoạt động hiệu quả hơn cho bạn. diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index 6b0c911c..236f1198 100755 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -147,8 +147,6 @@ This can take a couple of minutes." Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you From 701468ccd08ab840028bbe06cf06e57888fbbaab Mon Sep 17 00:00:00 2001 From: Cuynu Date: Fri, 10 May 2024 17:55:20 +0700 Subject: [PATCH 33/63] bump 24.17.17 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 053dafeb..0ef88b7d 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ buildscript { ext.androidBuildVersionTools = '33.0.2' ext.androidMinSdk = 23 - ext.androidTargetSdk = 29 + ext.androidTargetSdk = 30 ext.androidCompileSdk = 33 repositories { @@ -48,7 +48,7 @@ buildscript { } -def gmsVersion = "23.49.14" +def gmsVersion = "24.17.17" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) def ourVersionBase = "0.3.0" From 19590c5331c38020ab5a31a60d7691dc435c9c41 Mon Sep 17 00:00:00 2001 From: Cuynu Date: Fri, 10 May 2024 18:06:20 +0700 Subject: [PATCH 34/63] remove useless strings --- play-services-core/src/main/res/values-ar/strings.xml | 2 -- play-services-core/src/main/res/values-az/strings.xml | 2 -- play-services-core/src/main/res/values-be/strings.xml | 2 -- play-services-core/src/main/res/values-bn/strings.xml | 2 -- play-services-core/src/main/res/values-cs/strings.xml | 2 -- play-services-core/src/main/res/values-es-rES/strings.xml | 2 -- play-services-core/src/main/res/values-fr/strings.xml | 2 -- play-services-core/src/main/res/values-he/strings.xml | 2 -- play-services-core/src/main/res/values-hi/strings.xml | 2 -- play-services-core/src/main/res/values-id/strings.xml | 2 -- play-services-core/src/main/res/values-it/strings.xml | 2 -- play-services-core/src/main/res/values-ja/strings.xml | 2 -- play-services-core/src/main/res/values-ko/strings.xml | 2 -- play-services-core/src/main/res/values-lt/strings.xml | 2 -- play-services-core/src/main/res/values-pt-rBR/strings.xml | 2 -- play-services-core/src/main/res/values-ru/strings.xml | 2 -- play-services-core/src/main/res/values-th/strings.xml | 2 -- play-services-core/src/main/res/values-tr/strings.xml | 2 -- play-services-core/src/main/res/values-uk/strings.xml | 2 -- play-services-core/src/main/res/values-zh-rCN/strings.xml | 2 -- play-services-core/src/main/res/values-zh-rTW/strings.xml | 2 -- 21 files changed, 42 deletions(-) diff --git a/play-services-core/src/main/res/values-ar/strings.xml b/play-services-core/src/main/res/values-ar/strings.xml index f6d596ad..5d4cd8ea 100644 --- a/play-services-core/src/main/res/values-ar/strings.xml +++ b/play-services-core/src/main/res/values-ar/strings.xml @@ -123,8 +123,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-az/strings.xml b/play-services-core/src/main/res/values-az/strings.xml index 6e44dab6..740a9b11 100644 --- a/play-services-core/src/main/res/values-az/strings.xml +++ b/play-services-core/src/main/res/values-az/strings.xml @@ -123,8 +123,6 @@ Bu bir neçə dəqiqə çəkə bilər." Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-be/strings.xml b/play-services-core/src/main/res/values-be/strings.xml index a7564fef..1152301e 100644 --- a/play-services-core/src/main/res/values-be/strings.xml +++ b/play-services-core/src/main/res/values-be/strings.xml @@ -123,8 +123,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-bn/strings.xml b/play-services-core/src/main/res/values-bn/strings.xml index 0da376b5..0156013d 100644 --- a/play-services-core/src/main/res/values-bn/strings.xml +++ b/play-services-core/src/main/res/values-bn/strings.xml @@ -124,8 +124,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-cs/strings.xml b/play-services-core/src/main/res/values-cs/strings.xml index caa42143..7e021dd8 100644 --- a/play-services-core/src/main/res/values-cs/strings.xml +++ b/play-services-core/src/main/res/values-cs/strings.xml @@ -120,8 +120,6 @@ Tato akce může trvat několik minut." Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-es-rES/strings.xml b/play-services-core/src/main/res/values-es-rES/strings.xml index c0776be7..5b090832 100644 --- a/play-services-core/src/main/res/values-es-rES/strings.xml +++ b/play-services-core/src/main/res/values-es-rES/strings.xml @@ -123,8 +123,6 @@ Esto puede tardar unos minutos." Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-fr/strings.xml b/play-services-core/src/main/res/values-fr/strings.xml index 063ecefb..0fcac240 100644 --- a/play-services-core/src/main/res/values-fr/strings.xml +++ b/play-services-core/src/main/res/values-fr/strings.xml @@ -123,8 +123,6 @@ Cela peut prendre quelques minutes." Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-he/strings.xml b/play-services-core/src/main/res/values-he/strings.xml index e9eebd46..95511eb1 100644 --- a/play-services-core/src/main/res/values-he/strings.xml +++ b/play-services-core/src/main/res/values-he/strings.xml @@ -70,8 +70,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-hi/strings.xml b/play-services-core/src/main/res/values-hi/strings.xml index f003ff3c..fdf8ea5a 100644 --- a/play-services-core/src/main/res/values-hi/strings.xml +++ b/play-services-core/src/main/res/values-hi/strings.xml @@ -122,8 +122,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-id/strings.xml b/play-services-core/src/main/res/values-id/strings.xml index 368d1d36..ac8b9869 100644 --- a/play-services-core/src/main/res/values-id/strings.xml +++ b/play-services-core/src/main/res/values-id/strings.xml @@ -121,8 +121,6 @@ Ini bisa berlangsung beberapa menit." Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-it/strings.xml b/play-services-core/src/main/res/values-it/strings.xml index 0ce1d642..07277b42 100755 --- a/play-services-core/src/main/res/values-it/strings.xml +++ b/play-services-core/src/main/res/values-it/strings.xml @@ -120,8 +120,6 @@ Questo potrà richiedere un paio di minuti" Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-ja/strings.xml b/play-services-core/src/main/res/values-ja/strings.xml index b8edd4e2..0d93256f 100644 --- a/play-services-core/src/main/res/values-ja/strings.xml +++ b/play-services-core/src/main/res/values-ja/strings.xml @@ -123,8 +123,6 @@ Google サーバーへの接続を許可する Orbot がインストールされている必要があります。\nまだ実装されていません Tor ネットワーク経由で Google に接続する - Vanced+ microG の署名を偽装して YouTube Vanced+ の再生バッファリングの問題を修正します - アプリの署名を偽装 (試験的) ビルド日時 YouTube上のデータを閲覧 YouTube のアクティビティを確認、削除できます。YouTube の利便性を向上させるために YouTube やその他の Google サービスのデータがどのように使用されているかを確認できます diff --git a/play-services-core/src/main/res/values-ko/strings.xml b/play-services-core/src/main/res/values-ko/strings.xml index cbfa8fed..fa18f6d5 100644 --- a/play-services-core/src/main/res/values-ko/strings.xml +++ b/play-services-core/src/main/res/values-ko/strings.xml @@ -123,8 +123,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-lt/strings.xml b/play-services-core/src/main/res/values-lt/strings.xml index 28105572..bdf153d4 100644 --- a/play-services-core/src/main/res/values-lt/strings.xml +++ b/play-services-core/src/main/res/values-lt/strings.xml @@ -123,8 +123,6 @@ Tai gali užtrukti keletą minučių." Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-pt-rBR/strings.xml b/play-services-core/src/main/res/values-pt-rBR/strings.xml index 26ec6c6e..1120864e 100644 --- a/play-services-core/src/main/res/values-pt-rBR/strings.xml +++ b/play-services-core/src/main/res/values-pt-rBR/strings.xml @@ -141,8 +141,6 @@ Isso pode demorar alguns minutos." Permitir conexão com servidores do Google Requer que o Orbot esteja instalado\nAinda não é compatível Conecte-se ao Google através da rede Tor - Falsificar chave de assinatura do Vanced+ microG para corrigir o problema de buffer de reprodução do YouTube - Falsificar chave de assinatura do aplicativo (EXPERIMENTAL) Data da Build Seus dados no YouTube Navegue ou exclua sua atividade no YouTube e descubra como seus dados fazem com que o YouTube e outros serviços do Google funcionem melhor para você diff --git a/play-services-core/src/main/res/values-ru/strings.xml b/play-services-core/src/main/res/values-ru/strings.xml index d8739b29..61269e80 100644 --- a/play-services-core/src/main/res/values-ru/strings.xml +++ b/play-services-core/src/main/res/values-ru/strings.xml @@ -123,8 +123,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-th/strings.xml b/play-services-core/src/main/res/values-th/strings.xml index f5a73636..3f2df561 100644 --- a/play-services-core/src/main/res/values-th/strings.xml +++ b/play-services-core/src/main/res/values-th/strings.xml @@ -111,8 +111,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-tr/strings.xml b/play-services-core/src/main/res/values-tr/strings.xml index 5774702e..d5f5ddf5 100644 --- a/play-services-core/src/main/res/values-tr/strings.xml +++ b/play-services-core/src/main/res/values-tr/strings.xml @@ -123,8 +123,6 @@ Bunu bilerek yaptıysanız, Google'ın oturum açma sayfasına bağlanmak için Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-uk/strings.xml b/play-services-core/src/main/res/values-uk/strings.xml index 389b436b..a63b5a67 100644 --- a/play-services-core/src/main/res/values-uk/strings.xml +++ b/play-services-core/src/main/res/values-uk/strings.xml @@ -123,8 +123,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you 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 index 72062991..5dc520b8 100644 --- a/play-services-core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -122,8 +122,6 @@ Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you diff --git a/play-services-core/src/main/res/values-zh-rTW/strings.xml b/play-services-core/src/main/res/values-zh-rTW/strings.xml index e5f749a1..f4105881 100644 --- a/play-services-core/src/main/res/values-zh-rTW/strings.xml +++ b/play-services-core/src/main/res/values-zh-rTW/strings.xml @@ -130,8 +130,6 @@ Vanced+ microG 會把 Google 帳戶名稱以外的識別位 Allow connecting to Google servers Requires Orbot to be installed\nNot yet supported Connect to Google via Tor network - Spoofs signature key of Vanced+ microG to fix YouTube Vanced+ playback buffering issue - Spoof app signature key (EXPERIMENTAL) Build date Your data in YouTube Browse or delete your YouTube activity, and discover how your data makes YouTube and other Google services work better for you From d97a1ea32bbe5e1e41c76a89e404d1d675bed269 Mon Sep 17 00:00:00 2001 From: Cuynu Date: Sat, 11 May 2024 07:46:07 +0000 Subject: [PATCH 35/63] hide open YouTube Vanced+ options until its first release --- .../src/main/res/xml/preferences_google_more.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/play-services-core/src/main/res/xml/preferences_google_more.xml b/play-services-core/src/main/res/xml/preferences_google_more.xml index 65a3c7e4..c018f3a7 100755 --- a/play-services-core/src/main/res/xml/preferences_google_more.xml +++ b/play-services-core/src/main/res/xml/preferences_google_more.xml @@ -75,7 +75,7 @@ android:title="@string/pref_auth_include_android_id_title"/> + android:summary="DD MM YYYY - nightly"> From ec9a57224f4b322491354ef756b7d3c58d51aa6e Mon Sep 17 00:00:00 2001 From: Cuynu Date: Mon, 29 Jul 2024 10:09:51 +0000 Subject: [PATCH 36/63] bump 24.28.34 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0ef88b7d..d7fe9a96 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ buildscript { } -def gmsVersion = "24.17.17" +def gmsVersion = "24.28.34" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) def ourVersionBase = "0.3.0" From ee29d87c72148cea462867e4cac53138176bf0b6 Mon Sep 17 00:00:00 2001 From: Cuynu Date: Mon, 29 Jul 2024 10:13:16 +0000 Subject: [PATCH 37/63] . --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90b361b6..29f47891 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This fork tweaks MicroG to be usable by applications that require Google authentication such as [YouTube Vanced+](https://gitlab.com/cuynu/vancedx). ## Warning! -Vanced+ microG probably does not works in ReVanced/RVX in future as recent changes from ReVanced/RVX to use with they specific MicroG called ReVanced MicroG with different package name ! +Vanced+ microG are currently **DOES NOT WORK** with ReVanced/RVX as they now uses their own ReVanced microG with specific package name! However, there are workaround : [Issues #15](https://gitlab.com/cuynu/VancedxMicroG/-/issues/15#note_1837372638) ## Notable changes From 8eec459e70de9f0fd9d87d0a9c677025aeaf6a8b Mon Sep 17 00:00:00 2001 From: Cuynu Date: Thu, 8 Aug 2024 14:08:42 +0700 Subject: [PATCH 38/63] update gradle 8.0.0 --- build.gradle | 2 +- fake-signature/build.gradle | 4 ++-- gradle.properties | 4 +++- gradle/wrapper/gradle-wrapper.properties | 2 +- play-services-api/build.gradle | 8 ++++++-- play-services-api/src/main/AndroidManifest.xml | 2 +- play-services-base-api/build.gradle | 8 ++++++-- play-services-base-api/src/main/AndroidManifest.xml | 2 +- play-services-base-core/build.gradle | 6 +++--- play-services-base-core/src/main/AndroidManifest.xml | 3 +-- play-services-base/build.gradle | 5 +++-- play-services-base/src/main/AndroidManifest.xml | 3 +-- play-services-basement/build.gradle | 6 ++++-- play-services-basement/src/main/AndroidManifest.xml | 2 +- play-services-core-proto/build.gradle | 4 ++-- play-services-core/build.gradle | 4 ++-- play-services-core/microg-ui-tools/build.gradle | 5 +++-- .../microg-ui-tools/src/main/AndroidManifest.xml | 2 +- play-services-iid-api/build.gradle | 8 ++++++-- play-services-iid-api/src/main/AndroidManifest.xml | 2 +- play-services-tasks/build.gradle | 5 +++-- play-services-tasks/src/main/AndroidManifest.xml | 2 +- 22 files changed, 53 insertions(+), 36 deletions(-) diff --git a/build.gradle b/build.gradle index d7fe9a96..60699d2e 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ buildscript { ext.wireVersion = '4.8.0' - ext.androidBuildGradleVersion = '7.4.2' + ext.androidBuildGradleVersion = '8.0.0' ext.androidBuildVersionTools = '33.0.2' ext.androidMinSdk = 23 diff --git a/fake-signature/build.gradle b/fake-signature/build.gradle index 19f0fc40..e04d9893 100644 --- a/fake-signature/build.gradle +++ b/fake-signature/build.gradle @@ -17,8 +17,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } } diff --git a/gradle.properties b/gradle.properties index 976747da..126b7d28 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,5 @@ android.useAndroidX=true -android.disableAutomaticComponentCreation=true org.gradle.jvmargs=-Xmx2048m -XX:+UseParallelGC +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b1624c47..42defcc9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/play-services-api/build.gradle b/play-services-api/build.gradle index e254f48d..20d8bea5 100644 --- a/play-services-api/build.gradle +++ b/play-services-api/build.gradle @@ -26,9 +26,13 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 17 + targetCompatibility = 17 } + buildFeatures { + aidl true + } + namespace 'org.microg.gms.api' } dependencies { diff --git a/play-services-api/src/main/AndroidManifest.xml b/play-services-api/src/main/AndroidManifest.xml index d4a4d07a..359a9b71 100644 --- a/play-services-api/src/main/AndroidManifest.xml +++ b/play-services-api/src/main/AndroidManifest.xml @@ -15,4 +15,4 @@ ~ limitations under the License. --> - + diff --git a/play-services-base-api/build.gradle b/play-services-base-api/build.gradle index b806da4c..23b6b07c 100644 --- a/play-services-base-api/build.gradle +++ b/play-services-base-api/build.gradle @@ -34,7 +34,11 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 17 + targetCompatibility = 17 } + buildFeatures { + aidl true + } + namespace 'org.microg.gms.base.api' } \ No newline at end of file diff --git a/play-services-base-api/src/main/AndroidManifest.xml b/play-services-base-api/src/main/AndroidManifest.xml index 7c8eeef3..055d07d5 100644 --- a/play-services-base-api/src/main/AndroidManifest.xml +++ b/play-services-base-api/src/main/AndroidManifest.xml @@ -3,4 +3,4 @@ ~ SPDX-FileCopyrightText: 2020, microG Project Team ~ SPDX-License-Identifier: Apache-2.0 --> - + diff --git a/play-services-base-core/build.gradle b/play-services-base-core/build.gradle index ad962700..34288f21 100644 --- a/play-services-base-core/build.gradle +++ b/play-services-base-core/build.gradle @@ -21,7 +21,6 @@ android { buildToolsVersion androidBuildVersionTools defaultConfig { - versionName version minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk @@ -33,7 +32,8 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 17 + targetCompatibility = 17 } + namespace 'org.microg.gms.base.core' } \ No newline at end of file diff --git a/play-services-base-core/src/main/AndroidManifest.xml b/play-services-base-core/src/main/AndroidManifest.xml index 39554ca3..5c321135 100644 --- a/play-services-base-core/src/main/AndroidManifest.xml +++ b/play-services-base-core/src/main/AndroidManifest.xml @@ -4,8 +4,7 @@ ~ SPDX-License-Identifier: Apache-2.0 --> + xmlns:android="http://schemas.android.com/apk/res/android"> - + - + diff --git a/play-services-core-proto/build.gradle b/play-services-core-proto/build.gradle index 2cfb351e..6edfcb4b 100644 --- a/play-services-core-proto/build.gradle +++ b/play-services-core-proto/build.gradle @@ -21,9 +21,9 @@ sourceSets { } compileKotlin { - kotlinOptions.jvmTarget = 1.8 + kotlinOptions.jvmTarget = 17 } compileTestKotlin { - kotlinOptions.jvmTarget = 1.8 + kotlinOptions.jvmTarget = 17 } \ No newline at end of file diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 5cb9aedd..a9d2351a 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -109,8 +109,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } packagingOptions { diff --git a/play-services-core/microg-ui-tools/build.gradle b/play-services-core/microg-ui-tools/build.gradle index b6b2018d..260aa6a5 100644 --- a/play-services-core/microg-ui-tools/build.gradle +++ b/play-services-core/microg-ui-tools/build.gradle @@ -28,14 +28,15 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 17 + targetCompatibility = 17 } lintOptions { // TODO: Remove MissingTranslation once we have stable strings and proper translations. disable 'MissingTranslation', 'GetLocales' } + namespace 'org.microg.tools.ui' } dependencies { diff --git a/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml b/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml index 004f299c..359a9b71 100644 --- a/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml +++ b/play-services-core/microg-ui-tools/src/main/AndroidManifest.xml @@ -15,4 +15,4 @@ ~ limitations under the License. --> - + diff --git a/play-services-iid-api/build.gradle b/play-services-iid-api/build.gradle index 603c53b4..34ed3abb 100644 --- a/play-services-iid-api/build.gradle +++ b/play-services-iid-api/build.gradle @@ -26,9 +26,13 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 17 + targetCompatibility = 17 } + buildFeatures { + aidl true + } + namespace 'org.microg.gms.iid.api' } dependencies { diff --git a/play-services-iid-api/src/main/AndroidManifest.xml b/play-services-iid-api/src/main/AndroidManifest.xml index e979258e..ca617cfb 100644 --- a/play-services-iid-api/src/main/AndroidManifest.xml +++ b/play-services-iid-api/src/main/AndroidManifest.xml @@ -15,4 +15,4 @@ ~ limitations under the License. --> - \ No newline at end of file + \ No newline at end of file diff --git a/play-services-tasks/build.gradle b/play-services-tasks/build.gradle index 0eb84bbf..068aeb92 100644 --- a/play-services-tasks/build.gradle +++ b/play-services-tasks/build.gradle @@ -22,9 +22,10 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 17 + targetCompatibility = 17 } + namespace 'org.microg.gms.tasks' } dependencies { diff --git a/play-services-tasks/src/main/AndroidManifest.xml b/play-services-tasks/src/main/AndroidManifest.xml index c7b833fe..c256eb44 100644 --- a/play-services-tasks/src/main/AndroidManifest.xml +++ b/play-services-tasks/src/main/AndroidManifest.xml @@ -4,4 +4,4 @@ ~ SPDX-License-Identifier: Apache-2.0 --> - + From fccdc9c28829b9f4f5c8244d66f198644edaec50 Mon Sep 17 00:00:00 2001 From: cuynu Date: Thu, 26 Sep 2024 23:17:21 +0700 Subject: [PATCH 39/63] add new profile --- .../src/main/res/xml/profile_bullhead_27.xml | 36 ------------------ .../src/main/res/xml/profile_googlevr.xml | 31 ++++++++++++++++ .../res/xml/profile_lineage_falcon_25.xml | 36 ------------------ .../res/xml/profile_lineage_xperiaxz2.xml | 34 +++++++++++++++++ .../src/main/res/xml/profile_redfin_33.xml | 37 ------------------- .../xml/profile_sdk_gphone64_x86_64_33.xml | 37 ------------------- 6 files changed, 65 insertions(+), 146 deletions(-) delete mode 100644 play-services-core/src/main/res/xml/profile_bullhead_27.xml create mode 100644 play-services-core/src/main/res/xml/profile_googlevr.xml delete mode 100644 play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml create mode 100644 play-services-core/src/main/res/xml/profile_lineage_xperiaxz2.xml delete mode 100644 play-services-core/src/main/res/xml/profile_redfin_33.xml delete mode 100644 play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml diff --git a/play-services-core/src/main/res/xml/profile_bullhead_27.xml b/play-services-core/src/main/res/xml/profile_bullhead_27.xml deleted file mode 100644 index 186a4356..00000000 --- a/play-services-core/src/main/res/xml/profile_bullhead_27.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/xml/profile_googlevr.xml b/play-services-core/src/main/res/xml/profile_googlevr.xml new file mode 100644 index 00000000..1d0c7fba --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_googlevr.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml b/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml deleted file mode 100644 index 57beb5d4..00000000 --- a/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/xml/profile_lineage_xperiaxz2.xml b/play-services-core/src/main/res/xml/profile_lineage_xperiaxz2.xml new file mode 100644 index 00000000..6aa36bfa --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_lineage_xperiaxz2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/profile_redfin_33.xml b/play-services-core/src/main/res/xml/profile_redfin_33.xml deleted file mode 100644 index 52c57f88..00000000 --- a/play-services-core/src/main/res/xml/profile_redfin_33.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml b/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml deleted file mode 100644 index b616c365..00000000 --- a/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 195673c14bd5709395e4b5f62acf60f0768fed75 Mon Sep 17 00:00:00 2001 From: cuynu Date: Thu, 26 Sep 2024 23:17:58 +0700 Subject: [PATCH 40/63] show serial --- .../main/res/xml/preferences_device_registration.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/play-services-core/src/main/res/xml/preferences_device_registration.xml b/play-services-core/src/main/res/xml/preferences_device_registration.xml index ecd4b469..91c35133 100755 --- a/play-services-core/src/main/res/xml/preferences_device_registration.xml +++ b/play-services-core/src/main/res/xml/preferences_device_registration.xml @@ -13,16 +13,16 @@ android:key="pref_device_profile" android:persistent="false" android:title="@string/profile_name" - tools:summary="Automatic (Google Pixel 3, Android 11)" /> + tools:summary="Automatic" /> + tools:summary="" /> + tools:summary="Last registration: XX hours ago" /> + tools:summary="" /> Date: Thu, 26 Sep 2024 23:19:37 +0700 Subject: [PATCH 41/63] set FlAG_IMMUTABLE flag --- .../src/main/java/org/microg/gms/gcm/McsService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 f4766b4a..343d1721 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 @@ -209,7 +209,7 @@ public class McsService extends Service implements Handler.Callback { super.onCreate(); TriggerReceiver.register(this); database = new GcmDatabase(this); - heartbeatIntent = PendingIntent.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), 0); + heartbeatIntent = PendingIntent.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), PendingIntent.FLAG_IMMUTABLE); alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); powerManager = (PowerManager) getSystemService(POWER_SERVICE); if (checkSelfPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") == PackageManager.PERMISSION_GRANTED) { @@ -300,7 +300,7 @@ public class McsService extends Service implements Handler.Callback { AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); long delay = getCurrentDelay(); logd(context, "Scheduling reconnect in " + delay / 1000 + " seconds..."); - PendingIntent pi = PendingIntent.getBroadcast(context, 1, new Intent(ACTION_RECONNECT, null, context, TriggerReceiver.class), 0); + PendingIntent pi = PendingIntent.getBroadcast(context, 1, new Intent(ACTION_RECONNECT, null, context, TriggerReceiver.class), PendingIntent.FLAG_IMMUTABLE); alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, pi); } From 81ea45af34edb0fde37ed2e1e65525e50785123e Mon Sep 17 00:00:00 2001 From: cuynu Date: Thu, 26 Sep 2024 23:20:02 +0700 Subject: [PATCH 42/63] android id must be visible --- .../org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt index a0b4ad13..ec274c5e 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt @@ -153,7 +153,7 @@ class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { androidId.summary = serviceInfo.androidId.toString(16) } else { status.summary = getString(R.string.checkin_not_registered) - androidId.isVisible = false + androidId.isVisible = true } } } From 320320c24a4545757823d5eeaa284519ad35d1f8 Mon Sep 17 00:00:00 2001 From: cuynu Date: Thu, 26 Sep 2024 23:20:33 +0700 Subject: [PATCH 43/63] set FLAG_IMMUTABLE flag --- .../src/main/java/org/microg/gms/checkin/CheckinService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java index bd5bfabb..8f00098f 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java @@ -124,7 +124,7 @@ public class CheckinService extends IntentService { static void schedule(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - PendingIntent pendingIntent = PendingIntent.getService(context, TriggerReceiver.class.getName().hashCode(), new Intent(context, TriggerReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getService(context, TriggerReceiver.class.getName().hashCode(), new Intent(context, TriggerReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); alarmManager.set(AlarmManager.RTC, Math.max(LastCheckinInfo.read(context).getLastCheckin() + REGULAR_CHECKIN_INTERVAL, System.currentTimeMillis() + BACKUP_CHECKIN_DELAY), pendingIntent); } } From 99177daf0fc8dcde2c3f788d9f6203ebd3a623e4 Mon Sep 17 00:00:00 2001 From: cuynu Date: Thu, 26 Sep 2024 23:21:02 +0700 Subject: [PATCH 44/63] test compile with targetSdk 33 --- build.gradle | 2 +- .../src/main/AndroidManifest.xml | 43 ++++++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 60699d2e..e4c80045 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ buildscript { ext.androidBuildVersionTools = '33.0.2' ext.androidMinSdk = 23 - ext.androidTargetSdk = 30 + ext.androidTargetSdk = 33 ext.androidCompileSdk = 33 repositories { diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 7cf8a16f..dddc5dc6 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -25,14 +25,17 @@ android:name="${basePackageName}.android.c2dm.permission.RECEIVE" android:label="@string/perm_c2dm_receive_label" android:permissionGroup="android.permission-group.NETWORK" + tools:node="remove" android:protectionLevel="normal" /> @@ -53,7 +56,7 @@ - + @@ -62,7 +65,8 @@ - + @@ -116,13 +120,15 @@ - + - + @@ -150,6 +156,7 @@ @@ -161,6 +168,7 @@ @@ -173,6 +181,7 @@ @@ -185,6 +194,7 @@ @@ -208,6 +218,7 @@ @@ -220,7 +231,8 @@ - + @@ -278,6 +290,7 @@ @@ -285,19 +298,22 @@ - + - + - + @@ -306,6 +322,7 @@ @@ -409,14 +428,16 @@ - + - + From 31955e783e6578f682c966b03a7f1659332cfe97 Mon Sep 17 00:00:00 2001 From: cuynu Date: Thu, 26 Sep 2024 23:54:18 +0700 Subject: [PATCH 45/63] bump gradle 8.0.2 --- build.gradle | 2 +- play-services-core/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e4c80045..62b78afa 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ buildscript { ext.wireVersion = '4.8.0' - ext.androidBuildGradleVersion = '8.0.0' + ext.androidBuildGradleVersion = '8.0.2' ext.androidBuildVersionTools = '33.0.2' ext.androidMinSdk = 23 diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index a9d2351a..22e0b42c 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -57,7 +57,7 @@ android { defaultConfig { versionName version versionCode appVersionCode - resConfigs 'xxhdpi' + resourceConfigurations += ['xxhdpi'] minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk From 34a0e32b2ad181bb33c80a2173ee5f66e83e0201 Mon Sep 17 00:00:00 2001 From: cuynu Date: Thu, 26 Sep 2024 23:55:42 +0700 Subject: [PATCH 46/63] bump gms 24.38.32 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 62b78afa..6462d4ab 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ buildscript { } -def gmsVersion = "24.28.34" +def gmsVersion = "24.38.32" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) def ourVersionBase = "0.3.0" From 7a5ae620b23c2df5919710f5e8b2724572181615 Mon Sep 17 00:00:00 2001 From: cuynu Date: Thu, 26 Sep 2024 23:56:16 +0700 Subject: [PATCH 47/63] switch to monthly for now --- play-services-core/src/main/res/xml/preferences_google_more.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-core/src/main/res/xml/preferences_google_more.xml b/play-services-core/src/main/res/xml/preferences_google_more.xml index c018f3a7..a419362a 100755 --- a/play-services-core/src/main/res/xml/preferences_google_more.xml +++ b/play-services-core/src/main/res/xml/preferences_google_more.xml @@ -86,7 +86,7 @@ android:selectable="true" android:icon="@drawable/ic_info_outline" android:title="@string/build_date" - android:summary="DD MM YYYY - nightly"> + android:summary="DD MM YYYY - monthly"> From b3e0e54545462b028212fea72a903ca30cab3be5 Mon Sep 17 00:00:00 2001 From: cuynu Date: Fri, 27 Sep 2024 00:00:54 +0700 Subject: [PATCH 48/63] move serial preference to other category --- .../main/res/xml/preferences_device_registration.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/play-services-core/src/main/res/xml/preferences_device_registration.xml b/play-services-core/src/main/res/xml/preferences_device_registration.xml index 91c35133..4935bfb8 100755 --- a/play-services-core/src/main/res/xml/preferences_device_registration.xml +++ b/play-services-core/src/main/res/xml/preferences_device_registration.xml @@ -18,11 +18,6 @@ android:key="pref_device_profile_import" android:summary="@string/import_profile_summary" android:title="@string/import_profile_name" /> - + Date: Wed, 2 Oct 2024 16:18:37 +0700 Subject: [PATCH 49/63] Add preference for play licensing `microg@4ef2248` --- .../microg/gms/settings/SettingsContract.kt | 11 +++++ .../microg/gms/settings/SettingsProvider.kt | 21 ++++++++++ .../org/microg/gms/play/PlayPreferences.kt | 18 ++++++++ .../kotlin/org/microg/gms/ui/PlayFragment.kt | 42 +++++++++++++++++++ .../org/microg/gms/ui/SettingsFragment.kt | 15 +++++-- .../src/main/res/drawable/ic_play.xml | 10 +++++ .../src/main/res/navigation/nav_settings.xml | 10 +++++ .../src/main/res/values-ar/strings.xml | 5 +++ .../src/main/res/values-az/strings.xml | 5 +++ .../src/main/res/values-be/strings.xml | 5 +++ .../src/main/res/values-bn/strings.xml | 5 +++ .../src/main/res/values-cs/strings.xml | 5 +++ .../src/main/res/values-es-rES/strings.xml | 5 +++ .../src/main/res/values-fr/strings.xml | 5 +++ .../src/main/res/values-he/strings.xml | 5 +++ .../src/main/res/values-hi/strings.xml | 5 +++ .../src/main/res/values-id/strings.xml | 5 +++ .../src/main/res/values-it/strings.xml | 5 +++ .../src/main/res/values-ja/strings.xml | 5 +++ .../src/main/res/values-ko/strings.xml | 5 +++ .../src/main/res/values-lt/strings.xml | 5 +++ .../src/main/res/values-pt-rBR/strings.xml | 5 +++ .../src/main/res/values-ru/strings.xml | 5 +++ .../src/main/res/values-th/strings.xml | 5 +++ .../src/main/res/values-tr/strings.xml | 5 +++ .../src/main/res/values-uk/strings.xml | 5 +++ .../src/main/res/values-vi/strings.xml | 5 +++ .../src/main/res/values-zh-rCN/strings.xml | 5 +++ .../src/main/res/values-zh-rTW/strings.xml | 5 +++ .../src/main/res/values/strings.xml | 7 +++- .../main/res/xml/preferences_google_more.xml | 2 +- .../src/main/res/xml/preferences_play.xml | 12 ++++++ .../src/main/res/xml/preferences_start.xml | 5 +++ 33 files changed, 257 insertions(+), 6 deletions(-) create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/play/PlayPreferences.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/PlayFragment.kt create mode 100644 play-services-core/src/main/res/drawable/ic_play.xml create mode 100644 play-services-core/src/main/res/xml/preferences_play.xml diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 11d52bf9..eddfafe9 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -104,6 +104,17 @@ object SettingsContract { ) } + object Play { + private const val id = "play" + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" + const val LICENSING = "play_licensing" + val PROJECTION = arrayOf( + LICENSING + ) + } + + private fun withoutCallingIdentity(f: () -> T): T { val identity = Binder.clearCallingIdentity() try { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index 39dc00e1..15b797e3 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -14,6 +14,7 @@ import android.content.SharedPreferences import android.database.Cursor import android.database.MatrixCursor import android.net.Uri +import org.microg.gms.settings.SettingsContract.Play import android.preference.PreferenceManager import org.microg.gms.common.PackageUtils.warnIfNotMainProcess import org.microg.gms.settings.SettingsContract.Auth @@ -68,6 +69,7 @@ class SettingsProvider : ContentProvider() { Gcm.getContentUri(context!!) -> queryGcm(projection ?: Gcm.PROJECTION) Auth.getContentUri(context!!) -> queryAuth(projection ?: Auth.PROJECTION) Profile.getContentUri(context!!) -> queryProfile(projection ?: Profile.PROJECTION) + Play.getContentUri(context!!) -> queryPlay(projection ?: Play.PROJECTION) else -> null } @@ -84,11 +86,30 @@ class SettingsProvider : ContentProvider() { Gcm.getContentUri(context!!) -> updateGcm(values) Auth.getContentUri(context!!) -> updateAuth(values) Profile.getContentUri(context!!) -> updateProfile(values) + Play.getContentUri(context!!) -> updatePlay(values) else -> return 0 } return 1 } + private fun queryPlay(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> + when (key) { + Play.LICENSING -> getSettingsBoolean(key, false) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + private fun updatePlay(values: ContentValues) { + if (values.size() == 0) return + val editor = preferences.edit() + values.valueSet().forEach { (key, value) -> + when (key) { + Play.LICENSING -> editor.putBoolean(key, value as Boolean) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + editor.apply() + } + private fun queryCheckIn(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> when (key) { CheckIn.ENABLED -> getSettingsBoolean(key, true) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/play/PlayPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/play/PlayPreferences.kt new file mode 100644 index 00000000..fa906104 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/play/PlayPreferences.kt @@ -0,0 +1,18 @@ +package org.microg.gms.play +import android.content.Context +import org.microg.gms.settings.SettingsContract +object PlayPreferences { + @JvmStatic + fun isLicensingEnabled(context: Context): Boolean { + val projection = arrayOf(SettingsContract.Play.LICENSING) + return SettingsContract.getSettings(context, SettingsContract.Play.getContentUri(context), projection) { c -> + c.getInt(0) != 0 + } + } + @JvmStatic + fun setLicensingEnabled(context: Context, enabled: Boolean) { + SettingsContract.setSettings(context, SettingsContract.Play.getContentUri(context)) { + put(SettingsContract.Play.LICENSING, enabled) + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayFragment.kt new file mode 100644 index 00000000..eb7f1515 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayFragment.kt @@ -0,0 +1,42 @@ +package org.microg.gms.ui +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.TwoStatePreference +import com.mgoogle.android.gms.R +import org.microg.gms.play.PlayPreferences +class PlayFragment : PreferenceFragmentCompat() { + private lateinit var licensingEnabled: TwoStatePreference + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_play) + } + @SuppressLint("RestrictedApi") + override fun onBindPreferences() { + licensingEnabled = preferenceScreen.findPreference(PREF_LICENSING_ENABLED) ?: licensingEnabled + licensingEnabled.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext + lifecycleScope.launchWhenResumed { + if (newValue is Boolean) { + PlayPreferences.setLicensingEnabled(appContext, newValue) + } + updateContent() + } + true + } + } + override fun onResume() { + super.onResume() + updateContent() + } + private fun updateContent() { + val appContext = requireContext().applicationContext + lifecycleScope.launchWhenResumed { + licensingEnabled.isChecked = PlayPreferences.isLicensingEnabled(appContext) + } + } + companion object { + const val PREF_LICENSING_ENABLED = "play_licensing" + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index 86dd492d..e8cf7051 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -22,6 +22,7 @@ import org.microg.gms.settings.SettingsContract import org.microg.gms.ui.settings.SettingsProvider import org.microg.gms.ui.settings.getAllSettingsProviders import org.microg.tools.ui.ResourceSettingsFragment +import org.microg.gms.play.PlayPreferences import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -39,11 +40,17 @@ class SettingsFragment : ResourceSettingsFragment() { findNavController().navigate(requireContext(), R.id.openGcmSettings) true } - findPreference(PREF_ABOUT)?.setOnPreferenceClickListener { - findNavController().navigate(requireContext(), R.id.openAbout) + findPreference(PREF_PLAY)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openPlaySettings) true } - findPreference(PREF_ABOUT)?.summary = getString(R.string.about_version_str, AboutFragment.getSelfVersion(context)) + findPreference(PREF_ABOUT)!!.apply { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openAbout) + true + } + summary = getString(org.microg.tools.ui.R.string.about_version_str, AboutFragment.getSelfVersion(context)) + } findPreference(SettingsContract.CheckIn.HIDE_LAUNCHER_ICON)?.apply { setOnPreferenceChangeListener { _, newValue -> @@ -106,6 +113,7 @@ class SettingsFragment : ResourceSettingsFragment() { } findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPrefs.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_PLAY)!!.setSummary(if (PlayPreferences.isLicensingEnabled(requireContext())) R.string.pref_play_summary_licensing_on else R.string.pref_play_summary_licensing_off) lifecycleScope.launchWhenResumed { val entries = getAllSettingsProviders(requireContext()).flatMap { it.getEntriesDynamic(requireContext()) } @@ -124,6 +132,7 @@ class SettingsFragment : ResourceSettingsFragment() { const val PREF_ABOUT = "pref_about" const val PREF_GCM = "pref_gcm" const val PREF_CHECKIN = "pref_checkin" + const val PREF_PLAY = "pref_play" } init { diff --git a/play-services-core/src/main/res/drawable/ic_play.xml b/play-services-core/src/main/res/drawable/ic_play.xml new file mode 100644 index 00000000..9d8129f3 --- /dev/null +++ b/play-services-core/src/main/res/drawable/ic_play.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/navigation/nav_settings.xml b/play-services-core/src/main/res/navigation/nav_settings.xml index fb1cfa00..fd1a42ff 100755 --- a/play-services-core/src/main/res/navigation/nav_settings.xml +++ b/play-services-core/src/main/res/navigation/nav_settings.xml @@ -11,6 +11,9 @@ + + + diff --git a/play-services-core/src/main/res/values-ar/strings.xml b/play-services-core/src/main/res/values-ar/strings.xml index 5d4cd8ea..0043af17 100644 --- a/play-services-core/src/main/res/values-ar/strings.xml +++ b/play-services-core/src/main/res/values-ar/strings.xml @@ -138,4 +138,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-az/strings.xml b/play-services-core/src/main/res/values-az/strings.xml index 740a9b11..500e7ea7 100644 --- a/play-services-core/src/main/res/values-az/strings.xml +++ b/play-services-core/src/main/res/values-az/strings.xml @@ -138,4 +138,9 @@ Bu bir neçə dəqiqə çəkə bilər." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-be/strings.xml b/play-services-core/src/main/res/values-be/strings.xml index 1152301e..339f72ea 100644 --- a/play-services-core/src/main/res/values-be/strings.xml +++ b/play-services-core/src/main/res/values-be/strings.xml @@ -138,4 +138,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-bn/strings.xml b/play-services-core/src/main/res/values-bn/strings.xml index 0156013d..2c33052f 100644 --- a/play-services-core/src/main/res/values-bn/strings.xml +++ b/play-services-core/src/main/res/values-bn/strings.xml @@ -139,4 +139,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-cs/strings.xml b/play-services-core/src/main/res/values-cs/strings.xml index 7e021dd8..df04f144 100644 --- a/play-services-core/src/main/res/values-cs/strings.xml +++ b/play-services-core/src/main/res/values-cs/strings.xml @@ -135,4 +135,9 @@ Tato akce může trvat několik minut." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-es-rES/strings.xml b/play-services-core/src/main/res/values-es-rES/strings.xml index 5b090832..ef2e4b05 100644 --- a/play-services-core/src/main/res/values-es-rES/strings.xml +++ b/play-services-core/src/main/res/values-es-rES/strings.xml @@ -138,4 +138,9 @@ Esto puede tardar unos minutos." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-fr/strings.xml b/play-services-core/src/main/res/values-fr/strings.xml index 0fcac240..1fdd82f3 100644 --- a/play-services-core/src/main/res/values-fr/strings.xml +++ b/play-services-core/src/main/res/values-fr/strings.xml @@ -138,4 +138,9 @@ Cela peut prendre quelques minutes." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-he/strings.xml b/play-services-core/src/main/res/values-he/strings.xml index 95511eb1..ecd90758 100644 --- a/play-services-core/src/main/res/values-he/strings.xml +++ b/play-services-core/src/main/res/values-he/strings.xml @@ -85,4 +85,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-hi/strings.xml b/play-services-core/src/main/res/values-hi/strings.xml index fdf8ea5a..cad50b4c 100644 --- a/play-services-core/src/main/res/values-hi/strings.xml +++ b/play-services-core/src/main/res/values-hi/strings.xml @@ -137,4 +137,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-id/strings.xml b/play-services-core/src/main/res/values-id/strings.xml index ac8b9869..e00d9da4 100644 --- a/play-services-core/src/main/res/values-id/strings.xml +++ b/play-services-core/src/main/res/values-id/strings.xml @@ -136,4 +136,9 @@ Ini bisa berlangsung beberapa menit." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-it/strings.xml b/play-services-core/src/main/res/values-it/strings.xml index 07277b42..e73bffc2 100755 --- a/play-services-core/src/main/res/values-it/strings.xml +++ b/play-services-core/src/main/res/values-it/strings.xml @@ -135,4 +135,9 @@ Questo potrà richiedere un paio di minuti" Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-ja/strings.xml b/play-services-core/src/main/res/values-ja/strings.xml index 0d93256f..86fad15d 100644 --- a/play-services-core/src/main/res/values-ja/strings.xml +++ b/play-services-core/src/main/res/values-ja/strings.xml @@ -138,4 +138,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-ko/strings.xml b/play-services-core/src/main/res/values-ko/strings.xml index fa18f6d5..13b7a04f 100644 --- a/play-services-core/src/main/res/values-ko/strings.xml +++ b/play-services-core/src/main/res/values-ko/strings.xml @@ -138,4 +138,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-lt/strings.xml b/play-services-core/src/main/res/values-lt/strings.xml index bdf153d4..ce13b1f3 100644 --- a/play-services-core/src/main/res/values-lt/strings.xml +++ b/play-services-core/src/main/res/values-lt/strings.xml @@ -138,4 +138,9 @@ Tai gali užtrukti keletą minučių." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-pt-rBR/strings.xml b/play-services-core/src/main/res/values-pt-rBR/strings.xml index 1120864e..a819d634 100644 --- a/play-services-core/src/main/res/values-pt-rBR/strings.xml +++ b/play-services-core/src/main/res/values-pt-rBR/strings.xml @@ -156,4 +156,9 @@ Isso pode demorar alguns minutos." Gerenciar contas do Google conectadas Isso abrirá as configurações do seu dispositivo ! Configurações diversas + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-ru/strings.xml b/play-services-core/src/main/res/values-ru/strings.xml index 61269e80..00c399e6 100644 --- a/play-services-core/src/main/res/values-ru/strings.xml +++ b/play-services-core/src/main/res/values-ru/strings.xml @@ -138,4 +138,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-th/strings.xml b/play-services-core/src/main/res/values-th/strings.xml index 3f2df561..2f613b19 100644 --- a/play-services-core/src/main/res/values-th/strings.xml +++ b/play-services-core/src/main/res/values-th/strings.xml @@ -126,4 +126,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-tr/strings.xml b/play-services-core/src/main/res/values-tr/strings.xml index d5f5ddf5..24f96a11 100644 --- a/play-services-core/src/main/res/values-tr/strings.xml +++ b/play-services-core/src/main/res/values-tr/strings.xml @@ -138,4 +138,9 @@ Bunu bilerek yaptıysanız, Google'ın oturum açma sayfasına bağlanmak için Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-uk/strings.xml b/play-services-core/src/main/res/values-uk/strings.xml index a63b5a67..98e3dad5 100644 --- a/play-services-core/src/main/res/values-uk/strings.xml +++ b/play-services-core/src/main/res/values-uk/strings.xml @@ -138,4 +138,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-vi/strings.xml b/play-services-core/src/main/res/values-vi/strings.xml index df22a69d..33409f1d 100644 --- a/play-services-core/src/main/res/values-vi/strings.xml +++ b/play-services-core/src/main/res/values-vi/strings.xml @@ -141,4 +141,9 @@ Hãy thử lại sau." Quản lý tài khoản Google đã đăng nhập Điều này sẽ mở cài đặt thiết bị của bạn ! Cài đặt khác + Tự động cấp phép đang tắt + Tự động cấp phép đang bật + Tự động cấp phép + Một số ứng dụng có thể yêu cầu xác minh rằng bạn đã mua từ Google Play hay không. Khi ứng dụng đó yêu cầu, Vanced+ microG có thể chứng minh là bạn đã mua ứng dụng đó từ Google Play. Điều này sẽ không áp dụng nếu tùy chọn này được tắt hoặc không có tài khoản Google nào được thêm vào. + Dịch vụ Play 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 index 5dc520b8..31bb1f24 100644 --- a/play-services-core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -137,4 +137,9 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values-zh-rTW/strings.xml b/play-services-core/src/main/res/values-zh-rTW/strings.xml index f4105881..9c80ed44 100644 --- a/play-services-core/src/main/res/values-zh-rTW/strings.xml +++ b/play-services-core/src/main/res/values-zh-rTW/strings.xml @@ -145,4 +145,9 @@ Vanced+ microG 會把 Google 帳戶名稱以外的識別位 Manage signed in Google accounts This will open your device settings ! Miscellaneous settings + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Play Store services diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index 236f1198..c4cd7a0a 100755 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -119,7 +119,10 @@ This can take a couple of minutes." Messages: %1$d (%2$d bytes) Disconnected Connected since %1$s - + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. Allow registration Allow the app to register for push notifications. Start app on push message @@ -129,7 +132,7 @@ This can take a couple of minutes." Registered apps Unregistered apps Networks to use for push notifications - + Play Store services Device profile Select profile Native diff --git a/play-services-core/src/main/res/xml/preferences_google_more.xml b/play-services-core/src/main/res/xml/preferences_google_more.xml index a419362a..1c777be2 100755 --- a/play-services-core/src/main/res/xml/preferences_google_more.xml +++ b/play-services-core/src/main/res/xml/preferences_google_more.xml @@ -86,7 +86,7 @@ android:selectable="true" android:icon="@drawable/ic_info_outline" android:title="@string/build_date" - android:summary="DD MM YYYY - monthly"> + android:summary="27 September 2024 - monthly"> diff --git a/play-services-core/src/main/res/xml/preferences_play.xml b/play-services-core/src/main/res/xml/preferences_play.xml new file mode 100644 index 00000000..df28b60a --- /dev/null +++ b/play-services-core/src/main/res/xml/preferences_play.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/preferences_start.xml b/play-services-core/src/main/res/xml/preferences_start.xml index e7372cee..ae135d57 100755 --- a/play-services-core/src/main/res/xml/preferences_start.xml +++ b/play-services-core/src/main/res/xml/preferences_start.xml @@ -38,6 +38,11 @@ android:title="@string/hideLauncherIcon" android:icon="@drawable/ic_baseline_hide_source_24" android:defaultValue="true"/> + + Date: Wed, 2 Oct 2024 16:23:18 +0700 Subject: [PATCH 50/63] Expose settings via signature level permission "microg@05ca063" --- play-services-base-core/src/main/AndroidManifest.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/play-services-base-core/src/main/AndroidManifest.xml b/play-services-base-core/src/main/AndroidManifest.xml index 5c321135..013e3b18 100644 --- a/play-services-base-core/src/main/AndroidManifest.xml +++ b/play-services-base-core/src/main/AndroidManifest.xml @@ -6,11 +6,18 @@ + + + + android:exported="true" + android:readPermission="org.microg.gms.permission.READ_SETTINGS" + android:writePermission="org.microg.gms.permission.WRITE_SETTINGS" /> Date: Wed, 2 Oct 2024 16:25:30 +0700 Subject: [PATCH 51/63] No hardcoded application name in setting permission names "microg@53c683a" --- play-services-base-core/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/play-services-base-core/src/main/AndroidManifest.xml b/play-services-base-core/src/main/AndroidManifest.xml index 013e3b18..084e5119 100644 --- a/play-services-base-core/src/main/AndroidManifest.xml +++ b/play-services-base-core/src/main/AndroidManifest.xml @@ -6,9 +6,9 @@ - - From 82e7c9fd46f2a578249e54799996730b9a4741ed Mon Sep 17 00:00:00 2001 From: cuynu Date: Wed, 2 Oct 2024 16:26:10 +0700 Subject: [PATCH 52/63] No hardcoded application name in setting permission uses "microg@2b186fd" --- play-services-base-core/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/play-services-base-core/src/main/AndroidManifest.xml b/play-services-base-core/src/main/AndroidManifest.xml index 084e5119..369707e7 100644 --- a/play-services-base-core/src/main/AndroidManifest.xml +++ b/play-services-base-core/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ android:name="org.microg.gms.settings.SettingsProvider" android:authorities="${applicationId}.microg.settings" android:exported="true" - android:readPermission="org.microg.gms.permission.READ_SETTINGS" - android:writePermission="org.microg.gms.permission.WRITE_SETTINGS" /> + android:readPermission="${applicationId}.permission.READ_SETTINGS" + android:writePermission="${applicationId}.permission.WRITE_SETTINGS" /> Date: Wed, 2 Oct 2024 16:29:52 +0700 Subject: [PATCH 53/63] microG settings must be exported --- play-services-core/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index dddc5dc6..2a35d999 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -105,7 +105,7 @@ + android:exported="true" /> From 3f7d3cb596a21a3a05efa36e2426c8c225d95312 Mon Sep 17 00:00:00 2001 From: cuynu Date: Wed, 2 Oct 2024 17:12:35 +0700 Subject: [PATCH 54/63] Themes + Material3 improvements "microg@b62f089" "microg@4fc5b75" --- .../tools/ui/AbstractSettingsActivity.java | 3 ++- .../src/main/res/layout/settings_activity.xml | 8 ++++++ .../src/main/AndroidManifest.xml | 10 ++++---- ...ctivity.java => MainSettingsActivity.java} | 25 +++++++++++++------ .../preferences_material_switch_color.xml | 5 ++++ .../res/layout/preference_material_switch.xml | 11 ++++++++ .../main/res/layout/preference_switch_bar.xml | 6 ++--- .../res/layout/settings_root_activity.xml | 7 ++++++ .../src/main/res/values-night-v23/themes.xml | 8 ++++++ .../src/main/res/values-night-v27/themes.xml | 9 +++++++ .../src/main/res/values-night-v31/themes.xml | 21 +++++++--------- .../src/main/res/values-v23/themes.xml | 8 ++++++ .../src/main/res/values-v27/themes.xml | 9 +++++++ .../src/main/res/values-v31/themes.xml | 20 +++++++-------- .../src/main/res/values/themes.xml | 17 +++++++++++++ .../main/res/xml/preferences_google_more.xml | 2 +- 16 files changed, 128 insertions(+), 41 deletions(-) rename play-services-core/src/main/java/org/microg/gms/ui/{SettingsActivity.java => MainSettingsActivity.java} (66%) mode change 100755 => 100644 create mode 100644 play-services-core/src/main/res/color/preferences_material_switch_color.xml create mode 100644 play-services-core/src/main/res/layout/preference_material_switch.xml create mode 100644 play-services-core/src/main/res/values-night-v23/themes.xml create mode 100644 play-services-core/src/main/res/values-night-v27/themes.xml create mode 100644 play-services-core/src/main/res/values-v23/themes.xml create mode 100644 play-services-core/src/main/res/values-v27/themes.xml diff --git a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java index a771a9ea..2b0542a5 100644 --- a/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java +++ b/play-services-core/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java @@ -3,7 +3,7 @@ package org.microg.tools.ui; import android.os.Bundle; import android.view.MenuItem; import android.view.ViewGroup; - +import androidx.appcompat.widget.Toolbar; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; @@ -19,6 +19,7 @@ public abstract class AbstractSettingsActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.settings_activity); + setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); if (showHomeAsUp) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); } diff --git a/play-services-core/microg-ui-tools/src/main/res/layout/settings_activity.xml b/play-services-core/microg-ui-tools/src/main/res/layout/settings_activity.xml index a664de26..e5f71828 100644 --- a/play-services-core/microg-ui-tools/src/main/res/layout/settings_activity.xml +++ b/play-services-core/microg-ui-tools/src/main/res/layout/settings_activity.xml @@ -19,6 +19,14 @@ android:layout_height="match_parent" android:orientation="vertical"> + + + @@ -321,7 +321,7 @@ @@ -357,7 +357,7 @@ android:icon="@mipmap/ic_launcher" android:label="@string/gms_settings_name" android:process=":ui" - android:targetActivity="org.microg.gms.ui.SettingsActivity" + android:targetActivity="org.microg.gms.ui.MainSettingsActivity" android:taskAffinity="org.microg.gms.settings" android:theme="@style/AppTheme"> @@ -380,7 +380,7 @@ android:name="com.google.android.gms.app.settings.GoogleSettingsLink" android:process=":ui" android:exported="true" - android:targetActivity="org.microg.gms.ui.SettingsActivity" + android:targetActivity="org.microg.gms.ui.MainSettingsActivity" android:taskAffinity="org.microg.gms.settings"> diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/MainSettingsActivity.java old mode 100755 new mode 100644 similarity index 66% rename from play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java rename to play-services-core/src/main/java/org/microg/gms/ui/MainSettingsActivity.java index f0777081..b65b3806 --- a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/MainSettingsActivity.java @@ -1,7 +1,6 @@ package org.microg.gms.ui; -import static android.os.Build.VERSION.SDK_INT; - +import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; @@ -11,10 +10,12 @@ import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; -import com.google.android.material.color.DynamicColors; import com.mgoogle.android.gms.R; +import org.microg.gms.ui.settings.SettingsProvider; -public class SettingsActivity extends AppCompatActivity { +import static org.microg.gms.ui.settings.SettingsProviderKt.getAllSettingsProviders; + +public class MainSettingsActivity extends AppCompatActivity { private AppBarConfiguration appBarConfiguration; private NavController getNavController() { @@ -24,10 +25,18 @@ public class SettingsActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.settings_root_activity); - if (SDK_INT >= 31) { - DynamicColors.applyToActivityIfAvailable(this); + + Intent intent = getIntent(); + for (SettingsProvider settingsProvider : getAllSettingsProviders(this)) { + settingsProvider.preProcessSettingsIntent(intent); } + + setContentView(R.layout.settings_root_activity); + + for (SettingsProvider settingsProvider : getAllSettingsProviders(this)) { + settingsProvider.extendNavigation(getNavController()); + } + appBarConfiguration = new AppBarConfiguration.Builder(getNavController().getGraph()).build(); NavigationUI.setupActionBarWithNavController(this, getNavController(), appBarConfiguration); } @@ -36,4 +45,4 @@ public class SettingsActivity extends AppCompatActivity { public boolean onSupportNavigateUp() { return NavigationUI.navigateUp(getNavController(), appBarConfiguration) || super.onSupportNavigateUp(); } -} +} \ No newline at end of file diff --git a/play-services-core/src/main/res/color/preferences_material_switch_color.xml b/play-services-core/src/main/res/color/preferences_material_switch_color.xml new file mode 100644 index 00000000..1a6c9037 --- /dev/null +++ b/play-services-core/src/main/res/color/preferences_material_switch_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/preference_material_switch.xml b/play-services-core/src/main/res/layout/preference_material_switch.xml new file mode 100644 index 00000000..071caab2 --- /dev/null +++ b/play-services-core/src/main/res/layout/preference_material_switch.xml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/preference_switch_bar.xml b/play-services-core/src/main/res/layout/preference_switch_bar.xml index d45909cd..346b1dd4 100755 --- a/play-services-core/src/main/res/layout/preference_switch_bar.xml +++ b/play-services-core/src/main/res/layout/preference_switch_bar.xml @@ -52,9 +52,7 @@ android:maxLines="1" android:paddingStart="56dp" android:paddingLeft="56dp" - android:text="@{description}" - android:textSize="20sp" - android:textAppearance="@style/TextAppearance.M3.Sys.Typescale.HeadlineSmall" + android:textColor="@android:color/white" tools:text="Enabled" /> + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-night-v27/themes.xml b/play-services-core/src/main/res/values-night-v27/themes.xml new file mode 100644 index 00000000..0b22714a --- /dev/null +++ b/play-services-core/src/main/res/values-night-v27/themes.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-night-v31/themes.xml b/play-services-core/src/main/res/values-night-v31/themes.xml index de5386e8..7f89bd04 100644 --- a/play-services-core/src/main/res/values-night-v31/themes.xml +++ b/play-services-core/src/main/res/values-night-v31/themes.xml @@ -1,14 +1,11 @@ + - - + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-v23/themes.xml b/play-services-core/src/main/res/values-v23/themes.xml new file mode 100644 index 00000000..9009e5c5 --- /dev/null +++ b/play-services-core/src/main/res/values-v23/themes.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-v27/themes.xml b/play-services-core/src/main/res/values-v27/themes.xml new file mode 100644 index 00000000..434c86ec --- /dev/null +++ b/play-services-core/src/main/res/values-v27/themes.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-v31/themes.xml b/play-services-core/src/main/res/values-v31/themes.xml index 1719a989..6e96e2b4 100644 --- a/play-services-core/src/main/res/values-v31/themes.xml +++ b/play-services-core/src/main/res/values-v31/themes.xml @@ -1,13 +1,11 @@ + - - + \ No newline at end of file diff --git a/play-services-core/src/main/res/values/themes.xml b/play-services-core/src/main/res/values/themes.xml index d5cb5705..8e651928 100755 --- a/play-services-core/src/main/res/values/themes.xml +++ b/play-services-core/src/main/res/values/themes.xml @@ -22,6 +22,15 @@ @color/m3_theme_primary_dark + + + + + + + + diff --git a/play-services-core/src/main/res/xml/preferences_google_more.xml b/play-services-core/src/main/res/xml/preferences_google_more.xml index 1c777be2..0d348123 100755 --- a/play-services-core/src/main/res/xml/preferences_google_more.xml +++ b/play-services-core/src/main/res/xml/preferences_google_more.xml @@ -68,7 +68,7 @@ android:key="auth_manager_visible" android:summary="@string/pref_auth_visible_summary" android:title="@string/pref_auth_visible_title"/> - Date: Wed, 2 Oct 2024 17:34:44 +0700 Subject: [PATCH 55/63] update play icon --- play-services-core/src/main/res/drawable/ic_play.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/play-services-core/src/main/res/drawable/ic_play.xml b/play-services-core/src/main/res/drawable/ic_play.xml index 9d8129f3..9db10ec6 100644 --- a/play-services-core/src/main/res/drawable/ic_play.xml +++ b/play-services-core/src/main/res/drawable/ic_play.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorAccent"> + android:tint="?attr/colorPrimary"> \ No newline at end of file From b96a8d4727fd228c1b13ccd4aa3730a8a4e7ce35 Mon Sep 17 00:00:00 2001 From: cuynu Date: Wed, 2 Oct 2024 23:46:17 +0700 Subject: [PATCH 56/63] UI: Unify preferences "microg@d831bbc" --- build.gradle | 2 +- .../microg/gms/settings/SettingsContract.kt | 1 - .../res/layout/preference_app_heading.xml | 43 +++++ .../main/res/layout/preference_switch_bar.xml | 42 +++++ .../microg/gms/auth/login/LoginActivity.java | 6 +- .../microg/gms/checkin/CheckinManager.java | 4 +- .../microg/gms/checkin/CheckinService.java | 2 +- .../microg/gms/checkin/TriggerReceiver.java | 2 +- .../org/microg/gms/gcm/TriggerReceiver.java | 4 +- ...{CheckinPrefs.kt => CheckinPreferences.kt} | 26 ++- .../kotlin/org/microg/gms/gcm/GcmPrefs.kt | 11 +- .../org/microg/gms/gcm/PushRegisterService.kt | 4 +- .../org/microg/gms/ui/AppHeadingPreference.kt | 44 +++++ .../org/microg/gms/ui/AppIconPreference.kt | 3 +- .../kotlin/org/microg/gms/ui/AppPreference.kt | 47 +++++ .../gms/ui/DeviceRegistrationFragment.kt | 172 +++++++++++++++--- .../DeviceRegistrationPreferencesFragment.kt | 164 ----------------- .../gms/ui/PreferenceSwitchBarCallback.kt | 10 - .../gms/ui/PushNotificationAllAppsFragment.kt | 6 +- .../gms/ui/PushNotificationAppFragment.kt | 151 +++++++++++---- .../PushNotificationAppPreferencesFragment.kt | 137 -------------- .../microg/gms/ui/PushNotificationFragment.kt | 153 ++++++++++++---- .../ui/PushNotificationPreferencesFragment.kt | 128 ------------- .../org/microg/gms/ui/SettingsFragment.kt | 4 +- .../org/microg/gms/ui/SwitchBarPreference.kt | 51 ++++++ .../layout/device_registration_fragment.xml | 40 ---- .../res/layout/preference_app_heading.xml | 43 +++++ .../main/res/layout/preference_switch_bar.xml | 99 ++++------ .../layout/push_notification_app_fragment.xml | 81 --------- .../res/layout/push_notification_fragment.xml | 44 ----- .../xml/preferences_device_registration.xml | 9 +- .../xml/preferences_push_notifications.xml | 5 + .../preferences_push_notifications_app.xml | 5 + 33 files changed, 745 insertions(+), 798 deletions(-) create mode 100644 play-services-base-core/src/main/res/layout/preference_app_heading.xml create mode 100644 play-services-base-core/src/main/res/layout/preference_switch_bar.xml rename play-services-core/src/main/kotlin/org/microg/gms/checkin/{CheckinPrefs.kt => CheckinPreferences.kt} (60%) create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/AppHeadingPreference.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/AppPreference.kt delete mode 100755 play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt delete mode 100755 play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBarCallback.kt delete mode 100755 play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppPreferencesFragment.kt delete mode 100755 play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/SwitchBarPreference.kt delete mode 100755 play-services-core/src/main/res/layout/device_registration_fragment.xml create mode 100644 play-services-core/src/main/res/layout/preference_app_heading.xml delete mode 100755 play-services-core/src/main/res/layout/push_notification_app_fragment.xml delete mode 100755 play-services-core/src/main/res/layout/push_notification_fragment.xml diff --git a/build.gradle b/build.gradle index 6462d4ab..01047d0b 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ buildscript { ext.androidBuildVersionTools = '33.0.2' ext.androidMinSdk = 23 - ext.androidTargetSdk = 33 + ext.androidTargetSdk = 30 ext.androidCompileSdk = 33 repositories { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index eddfafe9..031b178d 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -22,7 +22,6 @@ object SettingsContract { const val SECURITY_TOKEN = "securityToken" const val VERSION_INFO = "versionInfo" const val DEVICE_DATA_VERSION_INFO = "deviceDataVersionInfo" - const val BRAND_SPOOF = "brandSpoof" const val HIDE_LAUNCHER_ICON = "hideLauncherIcon" diff --git a/play-services-base-core/src/main/res/layout/preference_app_heading.xml b/play-services-base-core/src/main/res/layout/preference_app_heading.xml new file mode 100644 index 00000000..cb7e4edf --- /dev/null +++ b/play-services-base-core/src/main/res/layout/preference_app_heading.xml @@ -0,0 +1,43 @@ + + + + + + + \ No newline at end of file diff --git a/play-services-base-core/src/main/res/layout/preference_switch_bar.xml b/play-services-base-core/src/main/res/layout/preference_switch_bar.xml new file mode 100644 index 00000000..9818305b --- /dev/null +++ b/play-services-base-core/src/main/res/layout/preference_switch_bar.xml @@ -0,0 +1,42 @@ + + + + + + + + \ No newline at end of file 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 5d836001..f52b93c4 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,9 +25,9 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT; import static org.microg.gms.auth.AuthPrefs.isAuthVisible; -import static org.microg.gms.checkin.CheckinPrefs.hideLauncherIcon; -import static org.microg.gms.checkin.CheckinPrefs.isSpoofingEnabled; -import static org.microg.gms.checkin.CheckinPrefs.setSpoofingEnabled; +import static org.microg.gms.checkin.CheckinPreferences.hideLauncherIcon; +import static org.microg.gms.checkin.CheckinPreferences.isSpoofingEnabled; +import static org.microg.gms.checkin.CheckinPreferences.setSpoofingEnabled; import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; import static org.microg.gms.common.Constants.GMS_VERSION_CODE; diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java index dc67b444..b9432ac6 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java @@ -16,7 +16,7 @@ package org.microg.gms.checkin; -import static org.microg.gms.checkin.CheckinPrefs.isSpoofingEnabled; +import static org.microg.gms.checkin.CheckinPreferences.isSpoofingEnabled; import android.accounts.Account; import android.accounts.AccountManager; @@ -43,7 +43,7 @@ public class CheckinManager { LastCheckinInfo info = LastCheckinInfo.read(context); if (!force && info.getLastCheckin() > System.currentTimeMillis() - MIN_CHECKIN_INTERVAL) return null; - if (!CheckinPrefs.isEnabled(context)) + if (!CheckinPreferences.isEnabled(context)) return null; List accounts = new ArrayList<>(); AccountManager accountManager = AccountManager.get(context); diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java index 8f00098f..3f53ebcd 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java @@ -80,7 +80,7 @@ public class CheckinService extends IntentService { protected void onHandleIntent(Intent intent) { try { ForegroundServiceContext.completeForegroundService(this, intent, TAG); - if (CheckinPrefs.isEnabled(this)) { + if (CheckinPreferences.isEnabled(this)) { LastCheckinInfo info = CheckinManager.checkin(this, intent.getBooleanExtra(EXTRA_FORCE_CHECKIN, false)); if (info != null) { Log.d(TAG, "Checked in as " + Long.toHexString(info.getAndroidId())); diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java index 752b8119..e41a954b 100755 --- a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java @@ -41,7 +41,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { try { boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction()); - if (CheckinPrefs.isEnabled(context) || force) { + if (CheckinPreferences.isEnabled(context) || force) { if (LastCheckinInfo.read(context).getLastCheckin() > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) { CheckinService.schedule(context); return; diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java index e94b2f02..83d182aa 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java @@ -30,7 +30,7 @@ import android.util.Log; import androidx.legacy.content.WakefulBroadcastReceiver; -import org.microg.gms.checkin.CheckinPrefs; +import org.microg.gms.checkin.CheckinPreferences; import org.microg.gms.checkin.LastCheckinInfo; import org.microg.gms.common.ForegroundServiceContext; @@ -67,7 +67,7 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { if (LastCheckinInfo.read(context).getAndroidId() == 0) { Log.d(TAG, "Ignoring " + intent + ": need to checkin first."); - if (CheckinPrefs.isEnabled(context)) { + if (CheckinPreferences.isEnabled(context)) { // Do a check-in if we are not actually checked in, // but should be, e.g. cleared app data Log.d(TAG, "Requesting check-in..."); diff --git a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPreferences.kt similarity index 60% rename from play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt rename to play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPreferences.kt index e2ab990a..eb78d498 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/checkin/CheckinPreferences.kt @@ -5,16 +5,21 @@ package org.microg.gms.checkin import android.content.Context +import android.content.Intent import org.microg.gms.settings.SettingsContract import org.microg.gms.settings.SettingsContract.CheckIn import org.microg.gms.settings.SettingsContract.setSettings -object CheckinPrefs { +object CheckinPreferences { @JvmStatic fun isEnabled(context: Context): Boolean { val projection = arrayOf(CheckIn.ENABLED) - return SettingsContract.getSettings(context, CheckIn.getContentUri(context), projection) { c -> + return SettingsContract.getSettings( + context, + CheckIn.getContentUri(context), + projection + ) { c -> c.getInt(0) != 0 } } @@ -22,7 +27,11 @@ object CheckinPrefs { @JvmStatic fun isSpoofingEnabled(context: Context): Boolean { val projection = arrayOf(CheckIn.BRAND_SPOOF) - return SettingsContract.getSettings(context, CheckIn.getContentUri(context), projection) { c -> + return SettingsContract.getSettings( + context, + CheckIn.getContentUri(context), + projection + ) { c -> c.getInt(0) != 0 } } @@ -41,4 +50,13 @@ object CheckinPrefs { } } -} + @JvmStatic + fun setEnabled(context: Context, enabled: Boolean) { + SettingsContract.setSettings(context, CheckIn.getContentUri(context)) { + put(CheckIn.ENABLED, enabled) + } + if (enabled) { + context.sendOrderedBroadcast(Intent(context, TriggerReceiver::class.java), null) + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt index 86e7a37d..c506ed62 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt @@ -71,6 +71,15 @@ data class GcmPrefs( gcmPrefs.setEnabled(context, config.enabled) } + fun setEnabled(context: Context, enabled: Boolean) { + val prefs = get(context) + setSettings(context, Gcm.getContentUri(context)) { + put(Gcm.ENABLE_GCM, enabled) + } + prefs.setEnabled(context, enabled) + } + + @JvmStatic fun clearLastPersistedId(context: Context) { setSettings(context, Gcm.getContentUri(context)) { @@ -79,6 +88,7 @@ data class GcmPrefs( } } + /** * Call this whenever the enabled state of GCM has changed. */ @@ -178,5 +188,4 @@ data class GcmPrefs( put(Gcm.LAST_PERSISTENT_ID, newId) } } - } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt index 1f27e4b6..8d3b9a78 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt @@ -16,7 +16,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope -import org.microg.gms.checkin.CheckinPrefs +import org.microg.gms.checkin.CheckinPreferences import org.microg.gms.checkin.CheckinService import org.microg.gms.checkin.LastCheckinInfo import org.microg.gms.common.ForegroundServiceContext @@ -29,7 +29,7 @@ import kotlin.coroutines.suspendCoroutine private const val TAG = "GmsGcmRegister" private suspend fun ensureCheckinIsUpToDate(context: Context) { - if (!CheckinPrefs.isEnabled(context)) throw RuntimeException("Checkin disabled") + if (!CheckinPreferences.isEnabled(context)) throw RuntimeException("Checkin disabled") val lastCheckin = LastCheckinInfo.read(context).lastCheckin if (lastCheckin < System.currentTimeMillis() - CheckinService.MAX_VALID_CHECKIN_AGE) { val resultData: Bundle = suspendCoroutine { continuation -> diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppHeadingPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppHeadingPreference.kt new file mode 100644 index 00000000..c3cf6185 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppHeadingPreference.kt @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.ui +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.net.Uri +import android.provider.Settings +import android.util.AttributeSet +import android.util.Log +import android.view.View +import android.widget.ImageView +import androidx.appcompat.content.res.AppCompatResources +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import org.microg.gms.base.core.R +class AppHeadingPreference : AppPreference, Preference.OnPreferenceClickListener { + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context) : super(context) + init { + layoutResource = R.layout.preference_app_heading + onPreferenceClickListener = this + } + override fun onPreferenceClick(preference: Preference): Boolean { + if (packageName != null) { + val intent = Intent() + intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + val uri: Uri = Uri.fromParts("package", packageName, null) + intent.data = uri + try { + context.startActivity(intent) + } catch (e: Exception) { + Log.w(TAG, "Failed to launch app", e) + } + return true + } else { + return false + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt index de5020c0..1570c691 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt @@ -11,8 +11,9 @@ import android.util.DisplayMetrics import android.widget.ImageView import androidx.preference.Preference import androidx.preference.PreferenceViewHolder +import android.content.pm.ApplicationInfo -class AppIconPreference : Preference { +class AppIconPreference : AppPreference { constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppPreference.kt new file mode 100644 index 00000000..27626231 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppPreference.kt @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.ui +import android.content.Context +import android.content.pm.ApplicationInfo +import android.util.AttributeSet +import androidx.appcompat.content.res.AppCompatResources +import androidx.preference.Preference +abstract class AppPreference : Preference { + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context) : super(context) + init { + isPersistent = false + } + private var packageNameField: String? = null + var applicationInfo: ApplicationInfo? + get() = context.packageManager.getApplicationInfoIfExists(packageNameField) + set(value) { + if (value == null && packageNameField != null) { + title = null + icon = null + } else if (value != null) { + val pm = context.packageManager + title = value.loadLabel(pm) ?: value.packageName + icon = value.loadIcon(pm) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) + } + packageNameField = value?.packageName + } + var packageName: String? + get() = packageNameField + set(value) { + if (value == null && packageNameField != null) { + title = null + icon = null + } else if (value != null) { + val pm = context.packageManager + val applicationInfo = pm.getApplicationInfoIfExists(value) + title = applicationInfo?.loadLabel(pm)?.toString() ?: value + icon = applicationInfo?.loadIcon(pm) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) + } + packageNameField = value + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt index 17887575..8c668529 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt @@ -5,51 +5,163 @@ package org.microg.gms.ui + +import android.annotation.SuppressLint +import android.net.Uri import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment +import android.os.Handler +import android.text.format.DateUtils +import android.util.Log +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.lifecycleScope +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat import com.mgoogle.android.gms.R -import com.mgoogle.android.gms.databinding.DeviceRegistrationFragmentBinding -import org.microg.gms.checkin.ServiceInfo +import org.microg.gms.checkin.CheckinPreferences import org.microg.gms.checkin.getCheckinServiceInfo -import org.microg.gms.checkin.setCheckinServiceConfiguration +import org.microg.gms.profile.ProfileManager +import org.microg.gms.profile.ProfileManager.PROFILE_AUTO +import org.microg.gms.profile.ProfileManager.PROFILE_NATIVE +import org.microg.gms.profile.ProfileManager.PROFILE_REAL +import org.microg.gms.profile.ProfileManager.PROFILE_SYSTEM +import org.microg.gms.profile.ProfileManager.PROFILE_USER +import java.io.File +import java.io.FileOutputStream -class DeviceRegistrationFragment : Fragment(R.layout.device_registration_fragment) { - private lateinit var binding: DeviceRegistrationFragmentBinding +class DeviceRegistrationFragment : PreferenceFragmentCompat() { + private lateinit var switchBarPreference: SwitchBarPreference + private lateinit var deviceProfile: ListPreference + private lateinit var importProfile: Preference + private lateinit var serial: Preference + private lateinit var statusCategory: PreferenceCategory + private lateinit var status: Preference + private lateinit var androidId: Preference + private val handler = Handler() + private val updateRunnable = Runnable { updateStatus() } + private lateinit var profileFileImport: ActivityResultLauncher - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = DeviceRegistrationFragmentBinding.inflate(inflater, container, false) - binding.switchBarCallback = object : PreferenceSwitchBarCallback { - override fun onChecked(newStatus: Boolean) { - setEnabled(newStatus) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + profileFileImport = registerForActivityResult(ActivityResultContracts.GetContent(), this::onFileSelected) + } + private fun onFileSelected(uri: Uri?) { + if (uri == null) return + try { + val context = requireContext() + val file = File.createTempFile("profile_", ".xml", context.cacheDir) + context.contentResolver.openInputStream(uri)?.use { inputStream -> + FileOutputStream(file).use { inputStream.copyTo(it) } + } + val success = ProfileManager.importUserProfile(context, file) + file.delete() + if (success && ProfileManager.isAutoProfile(context, PROFILE_USER)) { + ProfileManager.setProfile(context, PROFILE_USER) + } + updateStatus() + } catch (e: Exception) { + Log.w(TAG, e) + } + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_device_registration) + } + @SuppressLint("RestrictedApi") + override fun onBindPreferences() { + switchBarPreference = preferenceScreen.findPreference("pref_checkin_enabled") ?: switchBarPreference + deviceProfile = preferenceScreen.findPreference("pref_device_profile") ?: deviceProfile + importProfile = preferenceScreen.findPreference("pref_device_profile_import") ?: importProfile + serial = preferenceScreen.findPreference("pref_device_serial") ?: serial + statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory + status = preferenceScreen.findPreference("pref_device_registration_status") ?: status + androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId + deviceProfile.setOnPreferenceChangeListener { _, newValue -> + ProfileManager.setProfile(requireContext(), newValue as String? ?: PROFILE_AUTO) + updateStatus() + true + } + importProfile.setOnPreferenceClickListener { + profileFileImport.launch("text/xml") + true + } + switchBarPreference.setOnPreferenceChangeListener { _, newValue -> + val newStatus = newValue as Boolean + CheckinPreferences.setEnabled(requireContext(), newStatus) + true + } + } + private fun configureProfilePreference() { + val context = requireContext() + val configuredProfile = ProfileManager.getConfiguredProfile(context) + val autoProfile = ProfileManager.getAutoProfile(context) + val autoProfileName = when (autoProfile) { + PROFILE_NATIVE -> getString(R.string.profile_name_native) + PROFILE_REAL -> getString(R.string.profile_name_real) + else -> ProfileManager.getProfileName(context, autoProfile) + } + val profiles = + mutableListOf(PROFILE_AUTO, PROFILE_NATIVE, PROFILE_REAL) + val profileNames = mutableListOf(getString(R.string.profile_name_auto, autoProfileName), getString(R.string.profile_name_native), getString(R.string.profile_name_real)) + if (ProfileManager.hasProfile(context, PROFILE_SYSTEM)) { + profiles.add(PROFILE_SYSTEM) + profileNames.add(getString(R.string.profile_name_system, ProfileManager.getProfileName(context, PROFILE_SYSTEM))) + } + if (ProfileManager.hasProfile(context, PROFILE_USER)) { + profiles.add(PROFILE_USER) + profileNames.add(getString(R.string.profile_name_user, ProfileManager.getProfileName(context, PROFILE_USER))) + } + for (profile in R.xml::class.java.declaredFields.map { it.name } + .filter { it.startsWith("profile_") } + .map { it.substring(8) } + .sorted()) { + val profileName = ProfileManager.getProfileName(context, profile) + if (profileName != null) { + profiles.add(profile) + profileNames.add(profileName) } } - return binding.root - } - - fun setEnabled(newStatus: Boolean) { - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - val info = getCheckinServiceInfo(appContext) - val newConfiguration = info.configuration.copy(enabled = newStatus) - setCheckinServiceConfiguration(appContext, newConfiguration) - displayServiceInfo(info.copy(configuration = newConfiguration)) - } - } - - private fun displayServiceInfo(serviceInfo: ServiceInfo) { - binding.checkinEnabled = serviceInfo.configuration.enabled + deviceProfile.entryValues = profiles.toTypedArray() + deviceProfile.entries = profileNames.toTypedArray() + deviceProfile.value = configuredProfile + deviceProfile.summary = + profiles.indexOf(configuredProfile).takeIf { it >= 0 }?.let { profileNames[it] } ?: "Unknown" } override fun onResume() { super.onResume() + switchBarPreference.isChecked = CheckinPreferences.isEnabled(requireContext()) + updateStatus() + } + override fun onPause() { + super.onPause() + handler.removeCallbacks(updateRunnable) + } + private fun updateStatus() { + handler.removeCallbacks(updateRunnable) + handler.postDelayed(updateRunnable, UPDATE_INTERVAL) val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - displayServiceInfo(getCheckinServiceInfo(appContext)) + configureProfilePreference() + serial.summary = ProfileManager.getSerial(appContext) + val serviceInfo = getCheckinServiceInfo(appContext) + statusCategory.isVisible = serviceInfo.configuration.enabled + if (serviceInfo.lastCheckin > 0) { + status.summary = getString( + R.string.checkin_last_registration, + DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0) + ) + androidId.isVisible = true + androidId.summary = serviceInfo.androidId.toString(16) + } else { + status.summary = getString(R.string.checkin_not_registered) + androidId.isVisible = false + } } } + companion object { + private const val UPDATE_INTERVAL = 1000L + } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt deleted file mode 100755 index ec274c5e..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.ui - -import android.annotation.SuppressLint -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.text.format.DateUtils -import android.util.Log -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts -import androidx.lifecycle.lifecycleScope -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import com.mgoogle.android.gms.R -import org.microg.gms.checkin.getCheckinServiceInfo -import org.microg.gms.profile.ProfileManager -import org.microg.gms.profile.ProfileManager.PROFILE_AUTO -import org.microg.gms.profile.ProfileManager.PROFILE_NATIVE -import org.microg.gms.profile.ProfileManager.PROFILE_REAL -import org.microg.gms.profile.ProfileManager.PROFILE_SYSTEM -import org.microg.gms.profile.ProfileManager.PROFILE_USER -import java.io.File -import java.io.FileOutputStream - -@Suppress("Warnings") -class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { - private lateinit var deviceProfile: ListPreference - private lateinit var importProfile: Preference - private lateinit var serial: Preference - private lateinit var statusCategory: PreferenceCategory - private lateinit var status: Preference - private lateinit var androidId: Preference - private val handler = Handler() - private val updateRunnable = Runnable { updateStatus() } - private lateinit var profileFileImport: ActivityResultLauncher - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - profileFileImport = registerForActivityResult(ActivityResultContracts.GetContent(), this::onFileSelected) - } - - private fun onFileSelected(uri: Uri?) { - if (uri == null) return - try { - val context = requireContext() - val file = File.createTempFile("profile_", ".xml", context.cacheDir) - context.contentResolver.openInputStream(uri)?.use { inputStream -> - FileOutputStream(file).use { inputStream.copyTo(it) } - } - val success = ProfileManager.importUserProfile(context, file) - file.delete() - if (success && ProfileManager.isAutoProfile(context, PROFILE_USER)) { - ProfileManager.setProfile(context, PROFILE_USER) - } - updateStatus() - } catch (e: Exception) { - Log.w(TAG, e) - } - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_device_registration) - } - - @SuppressLint("RestrictedApi") - override fun onBindPreferences() { - deviceProfile = preferenceScreen.findPreference("pref_device_profile") ?: deviceProfile - importProfile = preferenceScreen.findPreference("pref_device_profile_import") ?: importProfile - serial = preferenceScreen.findPreference("pref_device_serial") ?: serial - statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory - status = preferenceScreen.findPreference("pref_device_registration_status") ?: status - androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId - - deviceProfile.setOnPreferenceChangeListener { _, newValue -> - ProfileManager.setProfile(requireContext(), newValue as String? ?: PROFILE_AUTO) - updateStatus() - true - } - importProfile.setOnPreferenceClickListener { - profileFileImport.launch("text/xml") - true - } - } - - private fun configureProfilePreference() { - val context = requireContext() - val configuredProfile = ProfileManager.getConfiguredProfile(context) - val autoProfileName = when (val autoProfile = ProfileManager.getAutoProfile(context)) { - PROFILE_NATIVE -> getString(R.string.profile_name_native) - PROFILE_REAL -> getString(R.string.profile_name_real) - else -> ProfileManager.getProfileName(context, autoProfile) - } - val profiles = - mutableListOf(PROFILE_AUTO, PROFILE_NATIVE, PROFILE_REAL) - val profileNames = mutableListOf(getString(R.string.profile_name_auto, autoProfileName), getString(R.string.profile_name_native), getString(R.string.profile_name_real)) - if (ProfileManager.hasProfile(context, PROFILE_SYSTEM)) { - profiles.add(PROFILE_SYSTEM) - profileNames.add(getString(R.string.profile_name_system, ProfileManager.getProfileName(context, PROFILE_SYSTEM))) - } - if (ProfileManager.hasProfile(context, PROFILE_USER)) { - profiles.add(PROFILE_USER) - profileNames.add(getString(R.string.profile_name_user, ProfileManager.getProfileName(context, PROFILE_USER))) - } - for (profile in R.xml::class.java.declaredFields.map { it.name } - .filter { it.startsWith("profile_") } - .map { it.substring(8) } - .sorted()) { - val profileName = ProfileManager.getProfileName(context, profile) - if (profileName != null) { - profiles.add(profile) - profileNames.add(profileName) - } - } - deviceProfile.entryValues = profiles.toTypedArray() - deviceProfile.entries = profileNames.toTypedArray() - deviceProfile.value = configuredProfile - deviceProfile.summary = - profiles.indexOf(configuredProfile).takeIf { it >= 0 }?.let { profileNames[it] } ?: "Unknown" - } - - override fun onResume() { - super.onResume() - updateStatus() - } - - override fun onPause() { - super.onPause() - handler.removeCallbacks(updateRunnable) - } - - private fun updateStatus() { - handler.removeCallbacks(updateRunnable) - handler.postDelayed(updateRunnable, UPDATE_INTERVAL) - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - configureProfilePreference() - serial.summary = ProfileManager.getSerial(appContext) - val serviceInfo = getCheckinServiceInfo(appContext) - statusCategory.isVisible = serviceInfo.configuration.enabled - if (serviceInfo.lastCheckin > 0) { - status.summary = getString( - R.string.checkin_last_registration, - DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0) - ) - androidId.isVisible = true - androidId.summary = serviceInfo.androidId.toString(16) - } else { - status.summary = getString(R.string.checkin_not_registered) - androidId.isVisible = true - } - } - } - - companion object { - private const val UPDATE_INTERVAL = 1000L - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBarCallback.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBarCallback.kt deleted file mode 100755 index b66f1119..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PreferenceSwitchBarCallback.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.ui - -interface PreferenceSwitchBarCallback { - fun onChecked(newStatus: Boolean) -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt index da3210e4..392e14d6 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt @@ -56,16 +56,12 @@ class PushNotificationAllAppsFragment : PreferenceFragmentCompat() { lifecycleScope.launchWhenResumed { val apps = withContext(Dispatchers.IO) { val res = database.appList.map { app -> - app to context.packageManager.getApplicationInfoIfExists(app.packageName) - }.map { (app, applicationInfo) -> val pref = AppIconPreference(context) - pref.title = applicationInfo?.loadLabel(context.packageManager) ?: app.packageName + pref.packageName = app.packageName pref.summary = when { app.lastMessageTimestamp > 0 -> getString(R.string.gcm_last_message_at, DateUtils.getRelativeTimeSpanString(app.lastMessageTimestamp)) else -> null } - pref.icon = applicationInfo?.loadIcon(context.packageManager) - ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openGcmAppDetailsFromAll, bundleOf( "package" to app.packageName diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt index f83a5e5d..780e85e1 100755 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt @@ -5,58 +5,135 @@ package org.microg.gms.ui -import android.content.Intent -import android.net.Uri +import android.annotation.SuppressLint import android.os.Bundle -import android.provider.Settings -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.content.res.AppCompatResources -import androidx.fragment.app.Fragment +import android.text.format.DateUtils +import androidx.appcompat.app.AlertDialog import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.TwoStatePreference import com.mgoogle.android.gms.R -import com.mgoogle.android.gms.databinding.PushNotificationAppFragmentBinding +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.gcm.GcmDatabase +import org.microg.gms.gcm.PushRegisterManager +class PushNotificationAppFragment : PreferenceFragmentCompat() { + private lateinit var appHeadingPreference: AppHeadingPreference + private lateinit var wakeForDelivery: TwoStatePreference + private lateinit var allowRegister: TwoStatePreference + private lateinit var status: Preference + private lateinit var unregister: Preference + private lateinit var unregisterCat: PreferenceCategory -class PushNotificationAppFragment : Fragment(R.layout.push_notification_fragment) { - lateinit var binding: PushNotificationAppFragmentBinding - val packageName: String? + private lateinit var database: GcmDatabase + private val packageName: String? get() = arguments?.getString("package") - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = PushNotificationAppFragmentBinding.inflate(inflater, container, false) - binding.callbacks = object : PushNotificationAppFragmentCallbacks { - override fun onAppClicked() { - val intent = Intent() - intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - val uri: Uri = Uri.fromParts("package", packageName, null) - intent.data = uri - try { - context!!.startActivity(intent) - } catch (e: Exception) { - Log.w(TAG, "Failed to launch app", e) + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_push_notifications_app) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + database = GcmDatabase(context) + } + + @SuppressLint("RestrictedApi") + override fun onBindPreferences() { + appHeadingPreference = preferenceScreen.findPreference("pref_push_app_heading") ?: appHeadingPreference + wakeForDelivery = preferenceScreen.findPreference("pref_push_app_wake_for_delivery") ?: wakeForDelivery + allowRegister = preferenceScreen.findPreference("pref_push_app_allow_register") ?: allowRegister + unregister = preferenceScreen.findPreference("pref_push_app_unregister") ?: unregister + unregisterCat = preferenceScreen.findPreference("prefcat_push_app_unregister") ?: unregisterCat + status = preferenceScreen.findPreference("pref_push_app_status") ?: status + wakeForDelivery.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + database.setAppWakeForDelivery(packageName, newValue as Boolean) + database.close() + true + } + allowRegister.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val enabled = newValue as? Boolean ?: return@OnPreferenceChangeListener false + if (!enabled) { + val registrations = packageName?.let { database.getRegistrationsByApp(it) } ?: emptyList() + if (registrations.isNotEmpty()) { + showUnregisterConfirm(R.string.gcm_unregister_after_deny_message) } } + database.setAppAllowRegister(packageName, enabled) + database.close() + true + } + unregister.onPreferenceClickListener = Preference.OnPreferenceClickListener { + showUnregisterConfirm(R.string.gcm_unregister_confirm_message) + true + } + } + + + private fun showUnregisterConfirm(unregisterConfirmDesc: Int) { + val pm = requireContext().packageManager + val applicationInfo = pm.getApplicationInfoIfExists(packageName) + AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.gcm_unregister_confirm_title, applicationInfo?.loadLabel(pm) + ?: packageName)) + .setMessage(unregisterConfirmDesc) + .setPositiveButton(android.R.string.yes) { _, _ -> unregister() } + .setNegativeButton(android.R.string.no) { _, _ -> }.show() + } + + private fun unregister() { + lifecycleScope.launchWhenResumed { + withContext(Dispatchers.IO) { + for (registration in database.getRegistrationsByApp(packageName)) { + PushRegisterManager.unregister(context, registration.packageName, registration.signature, null, null) + } + } + updateDetails() } - childFragmentManager.findFragmentById(R.id.sub_preferences)?.arguments = arguments - return binding.root } override fun onResume() { super.onResume() - val context = requireContext() + updateDetails() + } + + private fun updateDetails() { lifecycleScope.launchWhenResumed { - val pm = context.packageManager - val applicationInfo = pm.getApplicationInfoIfExists(packageName) - binding.appName = applicationInfo?.loadLabel(pm)?.toString() ?: packageName - binding.appIcon = applicationInfo?.loadIcon(pm) - ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) + appHeadingPreference.packageName = packageName + val app = packageName?.let { database.getApp(it) } + wakeForDelivery.isChecked = app?.wakeForDelivery ?: true + allowRegister.isChecked = app?.allowRegister ?: true + val registrations = packageName?.let { database.getRegistrationsByApp(it) } ?: emptyList() + unregisterCat.isVisible = registrations.isNotEmpty() + + val sb = StringBuilder() + if ((app?.totalMessageCount ?: 0L) == 0L) { + sb.append(getString(R.string.gcm_no_message_yet)) + } else { + sb.append(getString(R.string.gcm_messages_counter, app?.totalMessageCount, app?.totalMessageBytes)) + if (app?.lastMessageTimestamp != 0L) { + sb.append("\n").append(getString(R.string.gcm_last_message_at, DateUtils.getRelativeDateTimeString(context, app?.lastMessageTimestamp ?: 0L, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))) + } + } + for (registration in registrations) { + sb.append("\n") + if (registration.timestamp == 0L) { + sb.append(getString(R.string.gcm_registered)) + } else { + sb.append(getString(R.string.gcm_registered_since, DateUtils.getRelativeDateTimeString(context, registration.timestamp, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))) + } + } + status.summary = sb.toString() + + database.close() } } -} -interface PushNotificationAppFragmentCallbacks { - fun onAppClicked() -} + override fun onPause() { + super.onPause() + database.close() + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppPreferencesFragment.kt deleted file mode 100755 index 7106e506..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppPreferencesFragment.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ -@file:Suppress("DEPRECATION") - -package org.microg.gms.ui - -import android.annotation.SuppressLint -import android.os.Bundle -import android.text.format.DateUtils -import androidx.appcompat.app.AlertDialog -import androidx.lifecycle.lifecycleScope -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.TwoStatePreference -import com.mgoogle.android.gms.R -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.microg.gms.gcm.GcmDatabase -import org.microg.gms.gcm.PushRegisterManager - -class PushNotificationAppPreferencesFragment : PreferenceFragmentCompat() { - private lateinit var wakeForDelivery: TwoStatePreference - private lateinit var allowRegister: TwoStatePreference - private lateinit var status: Preference - private lateinit var unregister: Preference - private lateinit var unregisterCat: PreferenceCategory - - private lateinit var database: GcmDatabase - private val packageName: String? - get() = arguments?.getString("package") - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_push_notifications_app) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - database = GcmDatabase(context) - } - - @SuppressLint("RestrictedApi") - override fun onBindPreferences() { - wakeForDelivery = preferenceScreen.findPreference("pref_push_app_wake_for_delivery") ?: wakeForDelivery - allowRegister = preferenceScreen.findPreference("pref_push_app_allow_register") ?: allowRegister - unregister = preferenceScreen.findPreference("pref_push_app_unregister") ?: unregister - unregisterCat = preferenceScreen.findPreference("prefcat_push_app_unregister") ?: unregisterCat - status = preferenceScreen.findPreference("pref_push_app_status") ?: status - wakeForDelivery.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - database.setAppWakeForDelivery(packageName, newValue as Boolean) - database.close() - true - } - allowRegister.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - val enabled = newValue as? Boolean ?: return@OnPreferenceChangeListener false - if (!enabled) { - val registrations = packageName?.let { database.getRegistrationsByApp(it) } ?: emptyList() - if (registrations.isNotEmpty()) { - showUnregisterConfirm(R.string.gcm_unregister_after_deny_message) - } - } - database.setAppAllowRegister(packageName, enabled) - database.close() - true - } - unregister.onPreferenceClickListener = Preference.OnPreferenceClickListener { - showUnregisterConfirm(R.string.gcm_unregister_confirm_message) - true - } - } - - - private fun showUnregisterConfirm(unregisterConfirmDesc: Int) { - val pm = requireContext().packageManager - val applicationInfo = pm.getApplicationInfoIfExists(packageName) - AlertDialog.Builder(requireContext()) - .setTitle(getString(R.string.gcm_unregister_confirm_title, applicationInfo?.loadLabel(pm) - ?: packageName)) - .setMessage(unregisterConfirmDesc) - .setPositiveButton(android.R.string.yes) { _, _ -> unregister() } - .setNegativeButton(android.R.string.no) { _, _ -> }.show() - } - - private fun unregister() { - lifecycleScope.launchWhenResumed { - withContext(Dispatchers.IO) { - for (registration in database.getRegistrationsByApp(packageName)) { - PushRegisterManager.unregister(context, registration.packageName, registration.signature, null, null) - } - } - updateDetails() - } - } - - override fun onResume() { - super.onResume() - updateDetails() - } - - private fun updateDetails() { - lifecycleScope.launchWhenResumed { - val app = packageName?.let { database.getApp(it) } - wakeForDelivery.isChecked = app?.wakeForDelivery ?: true - allowRegister.isChecked = app?.allowRegister ?: true - val registrations = packageName?.let { database.getRegistrationsByApp(it) } ?: emptyList() - unregisterCat.isVisible = registrations.isNotEmpty() - - val sb = StringBuilder() - if ((app?.totalMessageCount ?: 0L) == 0L) { - sb.append(getString(R.string.gcm_no_message_yet)) - } else { - sb.append(getString(R.string.gcm_messages_counter, app?.totalMessageCount, app?.totalMessageBytes)) - if (app?.lastMessageTimestamp != 0L) { - sb.append("\n").append(getString(R.string.gcm_last_message_at, DateUtils.getRelativeDateTimeString(context, app?.lastMessageTimestamp ?: 0L, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))) - } - } - for (registration in registrations) { - sb.append("\n") - if (registration.timestamp == 0L) { - sb.append(getString(R.string.gcm_registered)) - } else { - sb.append(getString(R.string.gcm_registered_since, DateUtils.getRelativeDateTimeString(context, registration.timestamp, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))) - } - } - status.summary = sb.toString() - - database.close() - } - } - - override fun onPause() { - super.onPause() - database.close() - } -} 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 cb5aaa18..d05afb3d 100755 --- 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 @@ -2,61 +2,142 @@ * SPDX-FileCopyrightText: 2020, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ + package org.microg.gms.ui +import android.annotation.SuppressLint import android.os.Bundle -import android.view.* -import androidx.fragment.app.Fragment +import android.os.Handler +import android.text.format.DateUtils +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat import com.mgoogle.android.gms.R -import com.mgoogle.android.gms.databinding.PushNotificationFragmentBinding -import org.microg.gms.checkin.getCheckinServiceInfo -import org.microg.gms.gcm.ServiceInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.checkin.CheckinPreferences +import org.microg.gms.gcm.GcmDatabase +import org.microg.gms.gcm.GcmPrefs import org.microg.gms.gcm.getGcmServiceInfo -import org.microg.gms.gcm.setGcmServiceConfiguration -@Suppress("Warnings") -class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { - lateinit var binding: PushNotificationFragmentBinding +class PushNotificationFragment : PreferenceFragmentCompat() { + private lateinit var switchBarPreference: SwitchBarPreference + private lateinit var pushStatusCategory: PreferenceCategory + private lateinit var pushStatus: Preference + private lateinit var pushApps: PreferenceCategory + private lateinit var pushAppsAll: Preference + private lateinit var pushAppsNone: Preference + private lateinit var database: GcmDatabase + private val handler = Handler() + private val updateRunnable = Runnable { updateStatus() } - init { - setHasOptionsMenu(true) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + database = GcmDatabase(context) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = PushNotificationFragmentBinding.inflate(inflater, container, false) - binding.switchBarCallback = object : PreferenceSwitchBarCallback { - override fun onChecked(newStatus: Boolean) { - setEnabled(newStatus) - } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_push_notifications) + } + + @SuppressLint("RestrictedApi") + override fun onBindPreferences() { + switchBarPreference = preferenceScreen.findPreference("pref_push_enabled") ?: switchBarPreference + pushStatusCategory = preferenceScreen.findPreference("prefcat_push_status") ?: pushStatusCategory + pushStatus = preferenceScreen.findPreference("pref_push_status") ?: pushStatus + pushApps = preferenceScreen.findPreference("prefcat_push_apps") ?: pushApps + pushAppsAll = preferenceScreen.findPreference("pref_push_apps_all") ?: pushAppsAll + pushAppsNone = preferenceScreen.findPreference("pref_push_apps_none") ?: pushAppsNone + pushAppsAll.setOnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openAllGcmApps) + true } - return binding.root - } - - fun setEnabled(newStatus: Boolean) { - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - val info = getGcmServiceInfo(appContext) - val newConfiguration = info.configuration.copy(enabled = newStatus) - setGcmServiceConfiguration(appContext, newConfiguration) - displayServiceInfo(info.copy(configuration = newConfiguration)) + switchBarPreference.setOnPreferenceChangeListener { _, newValue -> + val newStatus = newValue as Boolean + GcmPrefs.setEnabled(requireContext(), newStatus) + true } } - private fun displayServiceInfo(serviceInfo: ServiceInfo) { - binding.gcmEnabled = serviceInfo.configuration.enabled - } - override fun onResume() { super.onResume() + + switchBarPreference.isEnabled = CheckinPreferences.isEnabled(requireContext()) + switchBarPreference.isChecked = GcmPrefs.get(requireContext()).isEnabled + + updateStatus() + updateContent() + } + + override fun onPause() { + super.onPause() + database.close() + handler.removeCallbacks(updateRunnable) + } + + private fun updateStatus() { + handler.postDelayed(updateRunnable, UPDATE_INTERVAL) val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - displayServiceInfo(getGcmServiceInfo(appContext)) - binding.checkinEnabled = getCheckinServiceInfo(appContext).configuration.enabled + lifecycleScope.launchWhenStarted { + val statusInfo = getGcmServiceInfo(appContext) + switchBarPreference.isChecked = statusInfo.configuration.enabled + pushStatusCategory.isVisible = statusInfo != null && statusInfo.configuration.enabled + pushStatus.summary = if (statusInfo != null && statusInfo.connected) { + appContext.getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(statusInfo.startTimestamp, System.currentTimeMillis(), 0)) + } else { + appContext.getString(R.string.gcm_network_state_disconnected) + } } } + private fun updateContent() { + lifecycleScope.launchWhenResumed { + val context = requireContext() + val (apps, showAll) = withContext(Dispatchers.IO) { + val apps = database.appList.sortedByDescending { it.lastMessageTimestamp } + val res = apps.map { app -> + app to context.packageManager.getApplicationInfoIfExists(app.packageName) + }.mapNotNull { (app, info) -> + if (info == null) null else app to info + }.take(3).mapIndexed { idx, (app, applicationInfo) -> + val pref = AppIconPreference(context) + pref.order = idx + pref.applicationInfo = applicationInfo + pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openGcmAppDetails, bundleOf( + "package" to app.packageName + )) + true + } + pref.key = "pref_push_app_" + app.packageName + pref + }.let { it to (it.size < apps.size) } + database.close() + res + } + pushAppsAll.isVisible = showAll + pushApps.removeAll() + for (app in apps) { + pushApps.addPreference(app) + } + if (showAll) { + pushApps.addPreference(pushAppsAll) + } else if (apps.isEmpty()) { + pushApps.addPreference(pushAppsNone) + } + } + } + + init { + setHasOptionsMenu(true) + } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced) super.onCreateOptionsMenu(menu, inflater) @@ -73,7 +154,7 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { } companion object { + private const val UPDATE_INTERVAL = 1000L private const val MENU_ADVANCED = Menu.FIRST } -} - +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt deleted file mode 100755 index da861c0b..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationPreferencesFragment.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ -@file:Suppress("DEPRECATION") - -package org.microg.gms.ui - -import android.annotation.SuppressLint -import android.os.Bundle -import android.os.Handler -import android.text.format.DateUtils -import androidx.core.os.bundleOf -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import com.mgoogle.android.gms.R -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.microg.gms.gcm.GcmDatabase -import org.microg.gms.gcm.getGcmServiceInfo - -class PushNotificationPreferencesFragment : PreferenceFragmentCompat() { - private lateinit var pushStatusCategory: PreferenceCategory - private lateinit var pushStatus: Preference - private lateinit var pushApps: PreferenceCategory - private lateinit var pushAppsAll: Preference - private lateinit var pushAppsNone: Preference - private lateinit var database: GcmDatabase - private val handler = Handler() - private val updateRunnable = Runnable { updateStatus() } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - database = GcmDatabase(context) - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_push_notifications) - } - - @SuppressLint("RestrictedApi") - override fun onBindPreferences() { - pushStatusCategory = preferenceScreen.findPreference("prefcat_push_status") ?: pushStatusCategory - pushStatus = preferenceScreen.findPreference("pref_push_status") ?: pushStatus - pushApps = preferenceScreen.findPreference("prefcat_push_apps") ?: pushApps - pushAppsAll = preferenceScreen.findPreference("pref_push_apps_all") ?: pushAppsAll - pushAppsNone = preferenceScreen.findPreference("pref_push_apps_none") ?: pushAppsNone - pushAppsAll.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(requireContext(), R.id.openAllGcmApps) - true - } - } - - override fun onResume() { - super.onResume() - updateStatus() - updateContent() - } - - override fun onPause() { - super.onPause() - database.close() - handler.removeCallbacks(updateRunnable) - } - - private fun updateStatus() { - try { - handler.postDelayed(updateRunnable, UPDATE_INTERVAL) - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenStarted { - val statusInfo = getGcmServiceInfo(appContext) - pushStatusCategory.isVisible = statusInfo.configuration.enabled - pushStatus.summary = if (statusInfo.connected) { - getString(R.string.gcm_network_state_connected, DateUtils.getRelativeTimeSpanString(statusInfo.startTimestamp, System.currentTimeMillis(), 0)) - } else { - getString(R.string.gcm_network_state_disconnected) - } - } - } catch (_: Exception) {} - } - - private fun updateContent() { - val appContext = requireContext().applicationContext - val context = requireContext() - lifecycleScope.launchWhenResumed { - val (apps, showAll) = withContext(Dispatchers.IO) { - val apps = database.appList.sortedByDescending { it.lastMessageTimestamp } - val res = apps.map { app -> - app to appContext.packageManager.getApplicationInfoIfExists(app.packageName) - }.mapNotNull { (app, info) -> - if (info == null) null else app to info - }.take(3).mapIndexed { idx, (app, applicationInfo) -> - val pref = AppIconPreference(appContext) - pref.order = idx - pref.title = applicationInfo.loadLabel(appContext.packageManager) - pref.icon = applicationInfo.loadIcon(appContext.packageManager) - pref.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(context, R.id.openGcmAppDetails, bundleOf( - "package" to app.packageName - )) - true - } - pref.key = "pref_push_app_" + app.packageName - pref - }.let { it to (it.size < apps.size) } - database.close() - res - } - pushAppsAll.isVisible = showAll - pushApps.removeAll() - for (app in apps) { - pushApps.addPreference(app) - } - if (showAll) { - pushApps.addPreference(pushAppsAll) - } else if (apps.isEmpty()) { - pushApps.addPreference(pushAppsNone) - } - } - } - - companion object { - private const val UPDATE_INTERVAL = 1000L - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index e8cf7051..fe98a953 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -15,7 +15,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.SwitchPreferenceCompat import com.mgoogle.android.gms.R -import org.microg.gms.checkin.CheckinPrefs +import org.microg.gms.checkin.CheckinPreferences import org.microg.gms.gcm.GcmDatabase import org.microg.gms.gcm.GcmPrefs import org.microg.gms.settings.SettingsContract @@ -112,7 +112,7 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_GCM)?.setSummary(R.string.service_status_disabled_short) } - findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPrefs.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPreferences.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) findPreference(PREF_PLAY)!!.setSummary(if (PlayPreferences.isLicensingEnabled(requireContext())) R.string.pref_play_summary_licensing_on else R.string.pref_play_summary_licensing_off) lifecycleScope.launchWhenResumed { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SwitchBarPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SwitchBarPreference.kt new file mode 100644 index 00000000..12802a1e --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SwitchBarPreference.kt @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.ui + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.SwitchCompat +import androidx.preference.PreferenceViewHolder +import androidx.preference.TwoStatePreference +import org.microg.gms.base.core.R + +// TODO +class SwitchBarPreference : TwoStatePreference { + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context) : super(context) + + init { + layoutResource = R.layout.preference_switch_bar + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + holder.isDividerAllowedBelow = false + holder.isDividerAllowedAbove = false + val switch = holder.findViewById(R.id.switch_widget) as SwitchCompat + switch.setOnCheckedChangeListener(null) + switch.isChecked = isChecked + switch.setOnCheckedChangeListener { view, isChecked -> + if (!callChangeListener(isChecked)) { + view.isChecked = !isChecked + return@setOnCheckedChangeListener + } + this.isChecked = isChecked + } + holder.itemView.setBackgroundColor(when { + isChecked -> androidx.appcompat.R.attr.colorControlActivated + isEnabled -> androidx.appcompat.R.attr.colorButtonNormal + else -> androidx.appcompat.R.attr.colorControlHighlight + }) + } +} + +@Deprecated("Get rid") +interface PreferenceSwitchBarCallback { + fun onChecked(newStatus: Boolean) +} \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/device_registration_fragment.xml b/play-services-core/src/main/res/layout/device_registration_fragment.xml deleted file mode 100755 index fe6d1e51..00000000 --- a/play-services-core/src/main/res/layout/device_registration_fragment.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/layout/preference_app_heading.xml b/play-services-core/src/main/res/layout/preference_app_heading.xml new file mode 100644 index 00000000..cb7e4edf --- /dev/null +++ b/play-services-core/src/main/res/layout/preference_app_heading.xml @@ -0,0 +1,43 @@ + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/preference_switch_bar.xml b/play-services-core/src/main/res/layout/preference_switch_bar.xml index 346b1dd4..9818305b 100755 --- a/play-services-core/src/main/res/layout/preference_switch_bar.xml +++ b/play-services-core/src/main/res/layout/preference_switch_bar.xml @@ -1,71 +1,42 @@ - - - + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:gravity="center" + android:orientation="horizontal" + android:paddingStart="?attr/listPreferredItemPaddingStart" + android:paddingLeft="?attr/listPreferredItemPaddingLeft" + android:paddingEnd="?attr/listPreferredItemPaddingEnd" + android:paddingRight="?attr/listPreferredItemPaddingRight" + tools:background="?attr/colorControlActivated"> - + - - - - - - - - - - - - - - - - + app:thumbTint="?android:attr/textColorPrimaryInverse" + app:trackTint="?android:attr/textColorSecondaryInverse" + tools:checked="true" /> + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/push_notification_app_fragment.xml b/play-services-core/src/main/res/layout/push_notification_app_fragment.xml deleted file mode 100755 index dcc6e7ad..00000000 --- a/play-services-core/src/main/res/layout/push_notification_app_fragment.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/layout/push_notification_fragment.xml b/play-services-core/src/main/res/layout/push_notification_fragment.xml deleted file mode 100755 index fdd799bd..00000000 --- a/play-services-core/src/main/res/layout/push_notification_fragment.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/play-services-core/src/main/res/xml/preferences_device_registration.xml b/play-services-core/src/main/res/xml/preferences_device_registration.xml index 4935bfb8..3fa45eb5 100755 --- a/play-services-core/src/main/res/xml/preferences_device_registration.xml +++ b/play-services-core/src/main/res/xml/preferences_device_registration.xml @@ -5,7 +5,14 @@ --> + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + diff --git a/play-services-core/src/main/res/xml/preferences_push_notifications.xml b/play-services-core/src/main/res/xml/preferences_push_notifications.xml index 9c250f95..416cfac8 100755 --- a/play-services-core/src/main/res/xml/preferences_push_notifications.xml +++ b/play-services-core/src/main/res/xml/preferences_push_notifications.xml @@ -7,6 +7,11 @@ + + diff --git a/play-services-core/src/main/res/xml/preferences_push_notifications_app.xml b/play-services-core/src/main/res/xml/preferences_push_notifications_app.xml index 01d83e52..5cf81e5b 100755 --- a/play-services-core/src/main/res/xml/preferences_push_notifications_app.xml +++ b/play-services-core/src/main/res/xml/preferences_push_notifications_app.xml @@ -6,6 +6,11 @@ + + From 299785daafeb1cc18115e88ee6b72fb06aa88f1f Mon Sep 17 00:00:00 2001 From: cuynu Date: Sat, 22 Mar 2025 15:34:55 +0700 Subject: [PATCH 57/63] up gradle 8.1 --- gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 29 ++++++++++++++---------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 42defcc9..0e1ae8ce 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ From d65e8c359c67500b0e41f2fc0a5c0285466beb7e Mon Sep 17 00:00:00 2001 From: cuynu Date: Sat, 22 Mar 2025 15:35:49 +0700 Subject: [PATCH 58/63] bump 25.09.34 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 01047d0b..bcf0bcbe 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ buildscript { } -def gmsVersion = "24.38.32" +def gmsVersion = "25.09.34" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) def ourVersionBase = "0.3.0" From d55ee374adcf20ac907b389ed9bdf5b5ffeb530c Mon Sep 17 00:00:00 2001 From: cuynu Date: Sat, 22 Mar 2025 15:48:45 +0700 Subject: [PATCH 59/63] make back to vanced+ option clickable --- .../src/main/res/xml/preferences_google_more.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/play-services-core/src/main/res/xml/preferences_google_more.xml b/play-services-core/src/main/res/xml/preferences_google_more.xml index 0d348123..69ff3a86 100755 --- a/play-services-core/src/main/res/xml/preferences_google_more.xml +++ b/play-services-core/src/main/res/xml/preferences_google_more.xml @@ -75,7 +75,6 @@ android:title="@string/pref_auth_include_android_id_title"/> + android:summary="DD MM YYYY - monthly"> From ef31eda600ada5eb49543964eb22855615fa045c Mon Sep 17 00:00:00 2001 From: cuynu Date: Sat, 22 Mar 2025 15:51:40 +0700 Subject: [PATCH 60/63] Revert "Add preference for play licensing `microg@4ef2248`" This reverts commit 9a12599b --- gradle/wrapper/gradle-wrapper.jar | Bin 61624 -> 43462 bytes .../microg/gms/settings/SettingsContract.kt | 11 ----- .../microg/gms/settings/SettingsProvider.kt | 21 --------- .../org/microg/gms/play/PlayPreferences.kt | 18 -------- .../kotlin/org/microg/gms/ui/PlayFragment.kt | 42 ------------------ .../src/main/res/navigation/nav_settings.xml | 10 ----- .../src/main/res/values-ar/strings.xml | 5 --- .../src/main/res/values-az/strings.xml | 5 --- .../src/main/res/values-be/strings.xml | 5 --- .../src/main/res/values-bn/strings.xml | 5 --- .../src/main/res/values-cs/strings.xml | 5 --- .../src/main/res/values-es-rES/strings.xml | 5 --- .../src/main/res/values-fr/strings.xml | 5 --- .../src/main/res/values-he/strings.xml | 5 --- .../src/main/res/values-hi/strings.xml | 5 --- .../src/main/res/values-id/strings.xml | 5 --- .../src/main/res/values-it/strings.xml | 5 --- .../src/main/res/values-ja/strings.xml | 5 --- .../src/main/res/values-ko/strings.xml | 5 --- .../src/main/res/values-lt/strings.xml | 5 --- .../src/main/res/values-pt-rBR/strings.xml | 5 --- .../src/main/res/values-ru/strings.xml | 5 --- .../src/main/res/values-th/strings.xml | 5 --- .../src/main/res/values-tr/strings.xml | 5 --- .../src/main/res/values-uk/strings.xml | 5 --- .../src/main/res/values-vi/strings.xml | 5 --- .../src/main/res/values-zh-rCN/strings.xml | 5 --- .../src/main/res/values-zh-rTW/strings.xml | 5 --- .../src/main/res/values/strings.xml | 7 +-- .../src/main/res/xml/preferences_play.xml | 12 ----- .../src/main/res/xml/preferences_start.xml | 5 --- 31 files changed, 2 insertions(+), 234 deletions(-) delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/play/PlayPreferences.kt delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/PlayFragment.kt delete mode 100644 play-services-core/src/main/res/xml/preferences_play.xml diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index afba109285af78dbd2a1d187e33ac4f87c76e392..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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

l-wUqma}v2c(1Oi^~*7`Q^d3B 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 literal 61624 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 diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 031b178d..8851d1a1 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -103,17 +103,6 @@ object SettingsContract { ) } - object Play { - private const val id = "play" - fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) - fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" - const val LICENSING = "play_licensing" - val PROJECTION = arrayOf( - LICENSING - ) - } - - private fun withoutCallingIdentity(f: () -> T): T { val identity = Binder.clearCallingIdentity() try { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index 15b797e3..39dc00e1 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -14,7 +14,6 @@ import android.content.SharedPreferences import android.database.Cursor import android.database.MatrixCursor import android.net.Uri -import org.microg.gms.settings.SettingsContract.Play import android.preference.PreferenceManager import org.microg.gms.common.PackageUtils.warnIfNotMainProcess import org.microg.gms.settings.SettingsContract.Auth @@ -69,7 +68,6 @@ class SettingsProvider : ContentProvider() { Gcm.getContentUri(context!!) -> queryGcm(projection ?: Gcm.PROJECTION) Auth.getContentUri(context!!) -> queryAuth(projection ?: Auth.PROJECTION) Profile.getContentUri(context!!) -> queryProfile(projection ?: Profile.PROJECTION) - Play.getContentUri(context!!) -> queryPlay(projection ?: Play.PROJECTION) else -> null } @@ -86,30 +84,11 @@ class SettingsProvider : ContentProvider() { Gcm.getContentUri(context!!) -> updateGcm(values) Auth.getContentUri(context!!) -> updateAuth(values) Profile.getContentUri(context!!) -> updateProfile(values) - Play.getContentUri(context!!) -> updatePlay(values) else -> return 0 } return 1 } - private fun queryPlay(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> - when (key) { - Play.LICENSING -> getSettingsBoolean(key, false) - else -> throw IllegalArgumentException("Unknown key: $key") - } - } - private fun updatePlay(values: ContentValues) { - if (values.size() == 0) return - val editor = preferences.edit() - values.valueSet().forEach { (key, value) -> - when (key) { - Play.LICENSING -> editor.putBoolean(key, value as Boolean) - else -> throw IllegalArgumentException("Unknown key: $key") - } - } - editor.apply() - } - private fun queryCheckIn(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> when (key) { CheckIn.ENABLED -> getSettingsBoolean(key, true) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/play/PlayPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/play/PlayPreferences.kt deleted file mode 100644 index fa906104..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/play/PlayPreferences.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.microg.gms.play -import android.content.Context -import org.microg.gms.settings.SettingsContract -object PlayPreferences { - @JvmStatic - fun isLicensingEnabled(context: Context): Boolean { - val projection = arrayOf(SettingsContract.Play.LICENSING) - return SettingsContract.getSettings(context, SettingsContract.Play.getContentUri(context), projection) { c -> - c.getInt(0) != 0 - } - } - @JvmStatic - fun setLicensingEnabled(context: Context, enabled: Boolean) { - SettingsContract.setSettings(context, SettingsContract.Play.getContentUri(context)) { - put(SettingsContract.Play.LICENSING, enabled) - } - } -} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayFragment.kt deleted file mode 100644 index eb7f1515..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PlayFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.microg.gms.ui -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.lifecycle.lifecycleScope -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.TwoStatePreference -import com.mgoogle.android.gms.R -import org.microg.gms.play.PlayPreferences -class PlayFragment : PreferenceFragmentCompat() { - private lateinit var licensingEnabled: TwoStatePreference - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.preferences_play) - } - @SuppressLint("RestrictedApi") - override fun onBindPreferences() { - licensingEnabled = preferenceScreen.findPreference(PREF_LICENSING_ENABLED) ?: licensingEnabled - licensingEnabled.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - if (newValue is Boolean) { - PlayPreferences.setLicensingEnabled(appContext, newValue) - } - updateContent() - } - true - } - } - override fun onResume() { - super.onResume() - updateContent() - } - private fun updateContent() { - val appContext = requireContext().applicationContext - lifecycleScope.launchWhenResumed { - licensingEnabled.isChecked = PlayPreferences.isLicensingEnabled(appContext) - } - } - companion object { - const val PREF_LICENSING_ENABLED = "play_licensing" - } -} \ No newline at end of file diff --git a/play-services-core/src/main/res/navigation/nav_settings.xml b/play-services-core/src/main/res/navigation/nav_settings.xml index fd1a42ff..fb1cfa00 100755 --- a/play-services-core/src/main/res/navigation/nav_settings.xml +++ b/play-services-core/src/main/res/navigation/nav_settings.xml @@ -11,9 +11,6 @@ - - - diff --git a/play-services-core/src/main/res/values-ar/strings.xml b/play-services-core/src/main/res/values-ar/strings.xml index 0043af17..5d4cd8ea 100644 --- a/play-services-core/src/main/res/values-ar/strings.xml +++ b/play-services-core/src/main/res/values-ar/strings.xml @@ -138,9 +138,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-az/strings.xml b/play-services-core/src/main/res/values-az/strings.xml index 500e7ea7..740a9b11 100644 --- a/play-services-core/src/main/res/values-az/strings.xml +++ b/play-services-core/src/main/res/values-az/strings.xml @@ -138,9 +138,4 @@ Bu bir neçə dəqiqə çəkə bilər." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-be/strings.xml b/play-services-core/src/main/res/values-be/strings.xml index 339f72ea..1152301e 100644 --- a/play-services-core/src/main/res/values-be/strings.xml +++ b/play-services-core/src/main/res/values-be/strings.xml @@ -138,9 +138,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-bn/strings.xml b/play-services-core/src/main/res/values-bn/strings.xml index 2c33052f..0156013d 100644 --- a/play-services-core/src/main/res/values-bn/strings.xml +++ b/play-services-core/src/main/res/values-bn/strings.xml @@ -139,9 +139,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-cs/strings.xml b/play-services-core/src/main/res/values-cs/strings.xml index df04f144..7e021dd8 100644 --- a/play-services-core/src/main/res/values-cs/strings.xml +++ b/play-services-core/src/main/res/values-cs/strings.xml @@ -135,9 +135,4 @@ Tato akce může trvat několik minut." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-es-rES/strings.xml b/play-services-core/src/main/res/values-es-rES/strings.xml index ef2e4b05..5b090832 100644 --- a/play-services-core/src/main/res/values-es-rES/strings.xml +++ b/play-services-core/src/main/res/values-es-rES/strings.xml @@ -138,9 +138,4 @@ Esto puede tardar unos minutos." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-fr/strings.xml b/play-services-core/src/main/res/values-fr/strings.xml index 1fdd82f3..0fcac240 100644 --- a/play-services-core/src/main/res/values-fr/strings.xml +++ b/play-services-core/src/main/res/values-fr/strings.xml @@ -138,9 +138,4 @@ Cela peut prendre quelques minutes." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-he/strings.xml b/play-services-core/src/main/res/values-he/strings.xml index ecd90758..95511eb1 100644 --- a/play-services-core/src/main/res/values-he/strings.xml +++ b/play-services-core/src/main/res/values-he/strings.xml @@ -85,9 +85,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-hi/strings.xml b/play-services-core/src/main/res/values-hi/strings.xml index cad50b4c..fdf8ea5a 100644 --- a/play-services-core/src/main/res/values-hi/strings.xml +++ b/play-services-core/src/main/res/values-hi/strings.xml @@ -137,9 +137,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-id/strings.xml b/play-services-core/src/main/res/values-id/strings.xml index e00d9da4..ac8b9869 100644 --- a/play-services-core/src/main/res/values-id/strings.xml +++ b/play-services-core/src/main/res/values-id/strings.xml @@ -136,9 +136,4 @@ Ini bisa berlangsung beberapa menit." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-it/strings.xml b/play-services-core/src/main/res/values-it/strings.xml index e73bffc2..07277b42 100755 --- a/play-services-core/src/main/res/values-it/strings.xml +++ b/play-services-core/src/main/res/values-it/strings.xml @@ -135,9 +135,4 @@ Questo potrà richiedere un paio di minuti" Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-ja/strings.xml b/play-services-core/src/main/res/values-ja/strings.xml index 86fad15d..0d93256f 100644 --- a/play-services-core/src/main/res/values-ja/strings.xml +++ b/play-services-core/src/main/res/values-ja/strings.xml @@ -138,9 +138,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-ko/strings.xml b/play-services-core/src/main/res/values-ko/strings.xml index 13b7a04f..fa18f6d5 100644 --- a/play-services-core/src/main/res/values-ko/strings.xml +++ b/play-services-core/src/main/res/values-ko/strings.xml @@ -138,9 +138,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-lt/strings.xml b/play-services-core/src/main/res/values-lt/strings.xml index ce13b1f3..bdf153d4 100644 --- a/play-services-core/src/main/res/values-lt/strings.xml +++ b/play-services-core/src/main/res/values-lt/strings.xml @@ -138,9 +138,4 @@ Tai gali užtrukti keletą minučių." Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-pt-rBR/strings.xml b/play-services-core/src/main/res/values-pt-rBR/strings.xml index a819d634..1120864e 100644 --- a/play-services-core/src/main/res/values-pt-rBR/strings.xml +++ b/play-services-core/src/main/res/values-pt-rBR/strings.xml @@ -156,9 +156,4 @@ Isso pode demorar alguns minutos." Gerenciar contas do Google conectadas Isso abrirá as configurações do seu dispositivo ! Configurações diversas - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-ru/strings.xml b/play-services-core/src/main/res/values-ru/strings.xml index 00c399e6..61269e80 100644 --- a/play-services-core/src/main/res/values-ru/strings.xml +++ b/play-services-core/src/main/res/values-ru/strings.xml @@ -138,9 +138,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-th/strings.xml b/play-services-core/src/main/res/values-th/strings.xml index 2f613b19..3f2df561 100644 --- a/play-services-core/src/main/res/values-th/strings.xml +++ b/play-services-core/src/main/res/values-th/strings.xml @@ -126,9 +126,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-tr/strings.xml b/play-services-core/src/main/res/values-tr/strings.xml index 24f96a11..d5f5ddf5 100644 --- a/play-services-core/src/main/res/values-tr/strings.xml +++ b/play-services-core/src/main/res/values-tr/strings.xml @@ -138,9 +138,4 @@ Bunu bilerek yaptıysanız, Google'ın oturum açma sayfasına bağlanmak için Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-uk/strings.xml b/play-services-core/src/main/res/values-uk/strings.xml index 98e3dad5..a63b5a67 100644 --- a/play-services-core/src/main/res/values-uk/strings.xml +++ b/play-services-core/src/main/res/values-uk/strings.xml @@ -138,9 +138,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-vi/strings.xml b/play-services-core/src/main/res/values-vi/strings.xml index 33409f1d..df22a69d 100644 --- a/play-services-core/src/main/res/values-vi/strings.xml +++ b/play-services-core/src/main/res/values-vi/strings.xml @@ -141,9 +141,4 @@ Hãy thử lại sau." Quản lý tài khoản Google đã đăng nhập Điều này sẽ mở cài đặt thiết bị của bạn ! Cài đặt khác - Tự động cấp phép đang tắt - Tự động cấp phép đang bật - Tự động cấp phép - Một số ứng dụng có thể yêu cầu xác minh rằng bạn đã mua từ Google Play hay không. Khi ứng dụng đó yêu cầu, Vanced+ microG có thể chứng minh là bạn đã mua ứng dụng đó từ Google Play. Điều này sẽ không áp dụng nếu tùy chọn này được tắt hoặc không có tài khoản Google nào được thêm vào. - Dịch vụ Play 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 index 31bb1f24..5dc520b8 100644 --- a/play-services-core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -137,9 +137,4 @@ Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values-zh-rTW/strings.xml b/play-services-core/src/main/res/values-zh-rTW/strings.xml index 9c80ed44..f4105881 100644 --- a/play-services-core/src/main/res/values-zh-rTW/strings.xml +++ b/play-services-core/src/main/res/values-zh-rTW/strings.xml @@ -145,9 +145,4 @@ Vanced+ microG 會把 Google 帳戶名稱以外的識別位 Manage signed in Google accounts This will open your device settings ! Miscellaneous settings - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. - Play Store services diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index c4cd7a0a..236f1198 100755 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -119,10 +119,7 @@ This can take a couple of minutes." Messages: %1$d (%2$d bytes) Disconnected Connected since %1$s - Licensing off - Licensing on - Answer license verification requests - Some apps require verification that you have purchased them on Google Play. When requested by an app, Vanced+ microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. + Allow registration Allow the app to register for push notifications. Start app on push message @@ -132,7 +129,7 @@ This can take a couple of minutes." Registered apps Unregistered apps Networks to use for push notifications - Play Store services + Device profile Select profile Native diff --git a/play-services-core/src/main/res/xml/preferences_play.xml b/play-services-core/src/main/res/xml/preferences_play.xml deleted file mode 100644 index df28b60a..00000000 --- a/play-services-core/src/main/res/xml/preferences_play.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/preferences_start.xml b/play-services-core/src/main/res/xml/preferences_start.xml index ae135d57..e7372cee 100755 --- a/play-services-core/src/main/res/xml/preferences_start.xml +++ b/play-services-core/src/main/res/xml/preferences_start.xml @@ -38,11 +38,6 @@ android:title="@string/hideLauncherIcon" android:icon="@drawable/ic_baseline_hide_source_24" android:defaultValue="true"/> - - Date: Sat, 22 Mar 2025 15:54:30 +0700 Subject: [PATCH 61/63] Revert "Add preference for play licensing `microg@4ef2248`" --- .../src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index fe98a953..e7f83c28 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -22,7 +22,6 @@ import org.microg.gms.settings.SettingsContract import org.microg.gms.ui.settings.SettingsProvider import org.microg.gms.ui.settings.getAllSettingsProviders import org.microg.tools.ui.ResourceSettingsFragment -import org.microg.gms.play.PlayPreferences import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -40,10 +39,6 @@ class SettingsFragment : ResourceSettingsFragment() { findNavController().navigate(requireContext(), R.id.openGcmSettings) true } - findPreference(PREF_PLAY)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(requireContext(), R.id.openPlaySettings) - true - } findPreference(PREF_ABOUT)!!.apply { onPreferenceClickListener = Preference.OnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openAbout) @@ -113,7 +108,6 @@ class SettingsFragment : ResourceSettingsFragment() { } findPreference(PREF_CHECKIN)?.setSummary(if (CheckinPreferences.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) - findPreference(PREF_PLAY)!!.setSummary(if (PlayPreferences.isLicensingEnabled(requireContext())) R.string.pref_play_summary_licensing_on else R.string.pref_play_summary_licensing_off) lifecycleScope.launchWhenResumed { val entries = getAllSettingsProviders(requireContext()).flatMap { it.getEntriesDynamic(requireContext()) } From 843497509c41e6541782bc68095785e41a32cf62 Mon Sep 17 00:00:00 2001 From: cuynu Date: Sat, 22 Mar 2025 16:30:00 +0700 Subject: [PATCH 62/63] add lavender profile --- .../profile_projectinfinityx_redminote7.xml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 play-services-core/src/main/res/xml/profile_projectinfinityx_redminote7.xml diff --git a/play-services-core/src/main/res/xml/profile_projectinfinityx_redminote7.xml b/play-services-core/src/main/res/xml/profile_projectinfinityx_redminote7.xml new file mode 100644 index 00000000..f92014ae --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_projectinfinityx_redminote7.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e8b5a02903d742aeb168dcd38aec838bc987cf24 Mon Sep 17 00:00:00 2001 From: Cuynu Date: Fri, 1 May 2026 14:46:20 +0700 Subject: [PATCH 63/63] Auth: improve handling of network and timeout exceptions https://github.com/microg/GmsCore/pull/3220 Signed-off-by: Cuynu --- .../org/microg/gms/auth/loginservice/AccountAuthenticator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 89ee692f..fa4eb2bc 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 @@ -104,7 +104,7 @@ class AccountAuthenticator extends AbstractAccountAuthenticator { } } catch (Exception e) { Log.w(TAG, e); - return null; + throw new NetworkErrorException(e); } return null; }