diff --git a/artwork/styles/style-microg-normal-openmaptiles.json b/artwork/styles/style-microg-normal-openmaptiles.json index 9ca3e71dd75c65cc5262aea85610479b85a0641d..ef11aa7d36cc89cde2c668a4c04081016c28ce40 100644 --- a/artwork/styles/style-microg-normal-openmaptiles.json +++ b/artwork/styles/style-microg-normal-openmaptiles.json @@ -1140,7 +1140,7 @@ "class" ], [ - "track" + "rail" ], true, false @@ -3250,27 +3250,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -3343,7 +3353,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -3392,6 +3404,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -3465,7 +3478,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -3507,6 +3522,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -3556,27 +3572,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -3649,7 +3675,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -3698,6 +3726,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -3771,7 +3800,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -3813,6 +3844,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -3862,27 +3894,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -3954,7 +3996,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -4003,6 +4047,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -4098,7 +4143,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -4140,6 +4187,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -4189,27 +4237,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -4281,7 +4339,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -4330,6 +4390,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -4425,7 +4486,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -4467,6 +4530,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", diff --git a/artwork/styles/style-microg-normal-stadia.json b/artwork/styles/style-microg-normal-stadia.json index af23da4c850b8bb5e5ce66fe981c5e604168052b..02dd3f032fb63d7f245f910f949bc75915a581ab 100644 --- a/artwork/styles/style-microg-normal-stadia.json +++ b/artwork/styles/style-microg-normal-stadia.json @@ -1288,16 +1288,32 @@ "filter": [ "all", [ - "match", + "any", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "rail" + ], + true, + false ], [ - "track" - ], - true, - false + "match", + [ + "get", + "subclass" + ], + [ + "light_rail", + "tram" + ], + true, + false + ] ], [ "match", @@ -3404,27 +3420,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -3497,7 +3523,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -3546,6 +3574,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -3619,7 +3648,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -3661,6 +3692,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -3710,27 +3742,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -3803,7 +3845,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -3852,6 +3896,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -3925,7 +3970,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -3967,6 +4014,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -4016,27 +4064,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -4108,7 +4166,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -4157,6 +4217,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -4252,7 +4313,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -4294,6 +4357,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -4343,27 +4407,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -4435,7 +4509,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -4484,6 +4560,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -4579,7 +4656,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -4621,6 +4700,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", diff --git a/artwork/styles/style-microg-satellite-stadia.json b/artwork/styles/style-microg-satellite-stadia.json index f83bfbe1fecb5a3e7edc5b6b7f14d32ed1c10823..7f183c5146e6089ea4e65384bd4964555fcdcf51 100644 --- a/artwork/styles/style-microg-satellite-stadia.json +++ b/artwork/styles/style-microg-satellite-stadia.json @@ -392,16 +392,32 @@ "filter": [ "all", [ - "match", + "any", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "rail" + ], + true, + false ], [ - "track" - ], - true, - false + "match", + [ + "get", + "subclass" + ], + [ + "light_rail", + "tram" + ], + true, + false + ] ], [ "match", @@ -2301,27 +2317,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -2394,7 +2420,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -2443,6 +2471,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -2516,7 +2545,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -2558,6 +2589,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -2607,27 +2639,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -2700,7 +2742,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -2749,6 +2793,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -2822,7 +2867,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -2864,6 +2911,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -2913,27 +2961,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -3005,7 +3063,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -3054,6 +3114,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -3149,7 +3210,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -3191,6 +3254,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", @@ -3240,27 +3304,37 @@ true ], [ - "!=", + "any", [ - "match", + "!=", [ - "get", - "class" + "match", + [ + "get", + "class" + ], + [ + "park", + "cemetery" + ], + true, + false ], [ - "park", - "cemetery" - ], - true, - false + "<=", + [ + "get", + "rank" + ], + 250 + ] ], [ - "<=", + ">=", [ - "get", - "rank" + "zoom" ], - 250 + 16 ] ] ], @@ -3332,7 +3406,9 @@ "poi_generic_green", [ "bank", + "atm", "parking", + "toilets", "garages" ], "poi_generic_purple", @@ -3381,6 +3457,7 @@ ] ], [ + "clothing_store", "shop" ], "poi_generic_blue", @@ -3476,7 +3553,9 @@ "hsl(117, 53%, 31%)", [ "bank", + "atm", "parking", + "toilets", "garages" ], "#737b9b", @@ -3518,6 +3597,7 @@ "#df7db1", [ "grocery", + "clothing_store", "shop" ], "hsl(213, 40%, 48%)", diff --git a/fake-signature/build.gradle b/fake-signature/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..5e6adbe3c61b3b2e2a7151205c182af6f5b83765 --- /dev/null +++ b/fake-signature/build.gradle @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' + +android { + namespace 'org.microg.signature.fake' + compileSdk androidCompileSdk + + defaultConfig { + minSdk androidMinSdk + targetSdk androidTargetSdk + } + + buildFeatures { + aidl = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { +} \ No newline at end of file diff --git a/fake-signature/src/main/AndroidManifest.xml b/fake-signature/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..6834b28a8b72ed524017f44c02c0ce91a4779eb3 --- /dev/null +++ b/fake-signature/src/main/AndroidManifest.xml @@ -0,0 +1,1190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fake-signature/src/main/aidl/com/huawei/signature/diff/ISignatureService.aidl b/fake-signature/src/main/aidl/com/huawei/signature/diff/ISignatureService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e3cf04ed8613ccdac1f784bb822d514aca751dc8 --- /dev/null +++ b/fake-signature/src/main/aidl/com/huawei/signature/diff/ISignatureService.aidl @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.huawei.signature.diff; + +/** + * Interface for Huawei Differentiated Signature Capability + * See https://forums.developer.huawei.com/forumPortal/en/topic/0202128603315033024 + */ +interface ISignatureService { + String[] querySignature(String packageName, boolean suggested); +} \ No newline at end of file diff --git a/fake-signature/src/main/java/com/huawei/signature/diff/AppListDatabaseOpenHelper.java b/fake-signature/src/main/java/com/huawei/signature/diff/AppListDatabaseOpenHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..8cb80e638c3b3ff810552c14642f9836ed831baf --- /dev/null +++ b/fake-signature/src/main/java/com/huawei/signature/diff/AppListDatabaseOpenHelper.java @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.huawei.signature.diff; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; +import org.microg.signature.fake.R; + +public class AppListDatabaseOpenHelper extends SQLiteOpenHelper { + private static final String TAG = AppListDatabaseOpenHelper.class.getSimpleName(); + private static final String DATABASE_NAME = "app_list.db"; + public static final String TABLE_APPLIST = "applist"; + public static final String COLUMN_NAME = "name"; + public static final String COLUMN_FAKE = "fake"; + private static final int DATABASE_VERSION = 2; + private static final String DROP_APP_LIST_TABLE = "DROP TABLE IF EXISTS " + TABLE_APPLIST; + private static final String CREATE_APP_LIST_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_APPLIST + "(" + + COLUMN_NAME + " VARCHAR(255) PRIMARY KEY, " + + COLUMN_FAKE + " INTEGER CHECK(" + COLUMN_FAKE + " >= 0 and " + COLUMN_FAKE + " <= 1)" + + ")"; + private final Context context; + + public AppListDatabaseOpenHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + this.context = context; + } + + @Override + public void onCreate(SQLiteDatabase db) { + Log.d(TAG, "onCreate"); + db.execSQL(DROP_APP_LIST_TABLE); + db.execSQL(CREATE_APP_LIST_TABLE); + initData(db); + } + + @Override + public void onOpen(SQLiteDatabase db) { + super.onOpen(db); + initData(db); + } + + private void initData(SQLiteDatabase db) { + String[] wantFakeApps = context.getResources().getStringArray(R.array.signature_want_fake); + String[] neverFakeApps = context.getResources().getStringArray(R.array.signature_never_fake); + if (wantFakeApps.length == 0 && neverFakeApps.length == 0) { + return; + } + for (String app : wantFakeApps) { + db.insertWithOnConflict(TABLE_APPLIST, null, generateValues(app, true), SQLiteDatabase.CONFLICT_IGNORE); + } + for (String app : neverFakeApps) { + db.insertWithOnConflict(TABLE_APPLIST, null, generateValues(app, false), SQLiteDatabase.CONFLICT_IGNORE); + } + } + + private ContentValues generateValues(String packageName, boolean fake) { + ContentValues contentValues = new ContentValues(); + contentValues.put(COLUMN_NAME, packageName); + contentValues.put(COLUMN_FAKE, fake ? 1 : 0); + return contentValues; + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onCreate(db); + } +} \ No newline at end of file diff --git a/fake-signature/src/main/java/com/huawei/signature/diff/InitProvider.java b/fake-signature/src/main/java/com/huawei/signature/diff/InitProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..e687b72a4656eafb7b801aaf23e3a87bbecf83c8 --- /dev/null +++ b/fake-signature/src/main/java/com/huawei/signature/diff/InitProvider.java @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.huawei.signature.diff; + +import android.app.ActivityManager; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; +import java.util.List; + +public class InitProvider extends ContentProvider { + private static final String TAG = InitProvider.class.getSimpleName(); + + @Override + public boolean onCreate() { + Log.d(TAG, "onCreate"); + if (!isServiceRunning(getContext(), getContext().getPackageName(), SignatureService.class.getName())) { + Intent intent = new Intent(getContext(), SignatureService.class); + try { + getContext().startService(intent); + } catch (Exception ignored) { + } + } + return false; + } + + private boolean isServiceRunning(Context context, String packageName, String serviceName) { + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List serviceInfoList = manager.getRunningServices(Integer.MAX_VALUE); + if (serviceInfoList == null) { + return false; + } + for (ActivityManager.RunningServiceInfo info : serviceInfoList) { + if (info.service.getPackageName().equals(packageName) && info.service.getClassName().equals(serviceName)) { + return true; + } + } + return false; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} \ No newline at end of file diff --git a/fake-signature/src/main/java/com/huawei/signature/diff/InitReceiver.java b/fake-signature/src/main/java/com/huawei/signature/diff/InitReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..c48dcf8a4866fa654d4827f19519e1212963f8b5 --- /dev/null +++ b/fake-signature/src/main/java/com/huawei/signature/diff/InitReceiver.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.huawei.signature.diff; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * This is to make sure the process is initialized at boot. + */ +public class InitReceiver extends BroadcastReceiver { + @SuppressLint("UnsafeProtectedBroadcastReceiver") + @Override + public void onReceive(Context context, Intent intent) { + // We don't need to do anything, the process is already started. + } +} diff --git a/fake-signature/src/main/java/com/huawei/signature/diff/SignatureService.java b/fake-signature/src/main/java/com/huawei/signature/diff/SignatureService.java new file mode 100644 index 0000000000000000000000000000000000000000..e0b99c4af068f6f595990019b634cf1f77a7da2d --- /dev/null +++ b/fake-signature/src/main/java/com/huawei/signature/diff/SignatureService.java @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.huawei.signature.diff; + +import static com.huawei.signature.diff.AppListDatabaseOpenHelper.COLUMN_NAME; +import static com.huawei.signature.diff.AppListDatabaseOpenHelper.TABLE_APPLIST; + +import android.app.Service; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.Log; +import org.microg.signature.fake.R; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Date; + +public class SignatureService extends Service { + private static final String TAG = "SignatureService"; + private SQLiteDatabase database; + private AppListDatabaseOpenHelper openHelper; + private long start; + + @Override + public void onCreate() { + super.onCreate(); + this.openHelper = new AppListDatabaseOpenHelper(this); + this.database = openHelper.getReadableDatabase(); + this.start = System.currentTimeMillis(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i(TAG, "onStartCommand"); + return START_STICKY; + } + + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public void onDestroy() { + this.openHelper.close(); + super.onDestroy(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println("Started: " + new Date(start)); + } + + private final ISignatureService.Stub binder = new ISignatureService.Stub() { + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + if (Binder.getCallingPid() > 10000) { + Log.w(TAG, "Illegal access from app"); + reply.writeException(new UnsupportedOperationException("Illegal")); + return true; + } + return super.onTransact(code, data, reply, flags); + } + + @Override + public String[] querySignature(String packageName, boolean suggested) throws RemoteException { + try (Cursor cursor = database.query(TABLE_APPLIST, null, COLUMN_NAME + "=?", + new String[]{packageName}, null, null, null)) { + switch (cursor.getCount()) { + case 0: + return getResult(suggested); + case 1: + if (cursor.moveToFirst()) { + int shouldFake = cursor.getInt(1); + return getResult(shouldFake == 1); + } + break; + default: + throw new IllegalArgumentException("result size: " + cursor.getCount()); + } + + } catch (Exception e) { + Log.w(TAG, e); + } + return getResult(false); + } + + private String[] getResult(boolean useFakeSignature) { + if (useFakeSignature) { + return new String[]{getString(R.string.fake_signature),}; + } else { + return new String[]{getString(R.string.real_signature),}; + } + } + }; +} \ 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 0000000000000000000000000000000000000000..5c7b0af376ff8aa51052b358aa890eb6743da771 --- /dev/null +++ b/fake-signature/src/main/res/values/arrays.xml @@ -0,0 +1,14 @@ + + + + + com.google.android.gms + com.android.vending + + + 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 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 df53f65ca40ff5268f0e78ac266a91b717a8b498..282e0dc1ff4f0d0cca92c613d91d76c4a15236cc 100644 --- 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-api/build.gradle b/play-services-api/build.gradle index a3728ac16aa094a031234be2d235d1580306d2e1..543aa7b49a419b1721d8ea603589e65abecd3bce 100644 --- a/play-services-api/build.gradle +++ b/play-services-api/build.gradle @@ -41,4 +41,5 @@ android { dependencies { api project(':play-services-base') + api project(':play-services-phenotype') } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/games/internal/IGamesCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/games/internal/IGamesCallbacks.aidl deleted file mode 100644 index fa1349db4d2664017efc4ecce26fd28b9803bfbe..0000000000000000000000000000000000000000 --- a/play-services-api/src/main/aidl/com/google/android/gms/games/internal/IGamesCallbacks.aidl +++ /dev/null @@ -1,47 +0,0 @@ -package com.google.android.gms.games.internal; - -import com.google.android.gms.common.data.DataHolder; -import com.google.android.gms.games.multiplayer.realtime.RealTimeMessage; - -interface IGamesCallbacks { - void onAuthTokenLoaded(int statusCode, String authToken) = 5000; - void onAchievementsLoaded(in DataHolder data) = 5001; - void onAchievementUpdated(int statusCode, String achievementId) = 5002; - void onLeaderboardsLoaded(in DataHolder data) = 5003; - void onLeaderboardScoresLoaded(in DataHolder leaderboard, in DataHolder scores) = 5004; - void onScoreSubmitted(in DataHolder data) = 5005; - void onPlayersLoaded(in DataHolder data) = 5006; - void onExtendedPlayersLoaded(in DataHolder data) = 5007; - void onGamesLoaded(in DataHolder data) = 5008; - void onExtendedGamesLoaded(in DataHolder data) = 5009; - void onGameInstancesLoaded(in DataHolder data) = 5010; - void onGameplayAclLoaded(in DataHolder data) = 5011; - void onGameplayAclUpdated(int statusCode) = 5012; - void onFAclLoaded(in DataHolder data) = 5013; - void onFAclUpdated(int statusCode) = 5014; - void onSignOutComplete() = 5015; - void onInvitationsLoaded(in DataHolder data) = 5016; - void onRoomCreated(in DataHolder data) = 5017; - void onJoinedRoom(in DataHolder data) = 5018; - void onLeftRoom(int statusCode, String roomId) = 5019; - void onRoomConnecting(in DataHolder data) = 5020; - void onRoomAutoMatching(in DataHolder data) = 5021; - void onRoomConnected(in DataHolder data) = 5022; - void onConnectedToRoom(in DataHolder data) = 5023; - void onDisconnectedFromRoom(in DataHolder data) = 5024; - void onPeerInvitedToRoom(in DataHolder data, in String[] participantIds) = 5025; - void onPeerJoinedRoom(in DataHolder data, in String[] participantIds) = 5026; - void onPeerLeftRoom(in DataHolder data, in String[] participantIds) = 5027; - void onPeerDeclined(in DataHolder data, in String[] participantIds) = 5028; - void onPeerConnected(in DataHolder data, in String[] participantIds) = 5029; - void onPeerDisconnected(in DataHolder data, in String[] participantIds) = 5030; - void onRealTimeMessageReceived(in RealTimeMessage message) = 5031; - void onMessageSent(int statusCode, int messageId, String recipientParticipantId) = 5032; - void onGameMuteStatusChanged(int statusCode, String externalGameId, boolean isMuted) = 5033; - void onNotifyAclLoaded(in DataHolder data) = 5034; - void onNotifyAclUpdated(int statusCode) = 5035; - void onInvitationReceived(in DataHolder data) = 5036; - void onGameMuteStatusLoaded(in DataHolder data) = 5037; - void onContactSettingsLoaded(in DataHolder data) = 5038; - void onContactSettingsUpdated(int statusCode) = 5039; -} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/games/internal/IGamesService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/games/internal/IGamesService.aidl deleted file mode 100644 index 1f8e7b4e20eec57f2a0002787d3aca4f5bc923c8..0000000000000000000000000000000000000000 --- a/play-services-api/src/main/aidl/com/google/android/gms/games/internal/IGamesService.aidl +++ /dev/null @@ -1,80 +0,0 @@ -package com.google.android.gms.games.internal; - -import android.net.Uri; -import android.os.Bundle; -import android.os.IBinder; -import com.google.android.gms.games.internal.IGamesCallbacks; -import com.google.android.gms.common.data.DataHolder; - -interface IGamesService { - void clientDisconnecting(long clientId) = 5000; - void signOut(IGamesCallbacks callbacks) = 5001; - String getAppId() = 5002; - Bundle getConnectionHint() = 5003; - void showWelcomePopup(IBinder windowToken, in Bundle extraArgs) = 5004; - void cancelPopups() = 5005; - String getCurrentAccountName() = 5006; - void loadGameplayAclInternal(IGamesCallbacks callbacks, String gameId) = 5007; - void updateGameplayAclInternal(IGamesCallbacks callbacks, String gameId, String aclData) = 5008; - void loadFAclInternal(IGamesCallbacks callbacks, String gameId) = 5009; - void updateFAclInternal(IGamesCallbacks callbacks, String gameId, boolean allCirclesVisible, in long[] circleIds) = 5010; - String getCurrentPlayerId() = 5011; - DataHolder getCurrentPlayer() = 5012; - void loadPlayer(IGamesCallbacks callbacks, String playerId) = 5013; - void loadInvitablePlayers(IGamesCallbacks callbacks, int pageSize, boolean expandCachedData, boolean forceReload) = 5014; - void submitScore(IGamesCallbacks callbacks, String leaderboardId, long score) = 5015; - void loadLeaderboards(IGamesCallbacks callbacks) = 5016; - void loadLeaderboard(IGamesCallbacks callbacks, String leaderboardId) = 5017; - void loadTopScores(IGamesCallbacks callbacks, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) = 5018; - void loadPlayerCenteredScores(IGamesCallbacks callbacks, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) = 5019; - void loadMoreScores(IGamesCallbacks callbacks, in Bundle previousheader, int maxResults, int pageDirection) = 5020; - void loadAchievements(IGamesCallbacks callbacks) = 5021; - void revealAchievement(IGamesCallbacks callbacks, String achievementId, IBinder windowToken, in Bundle extraArgs) = 5022; - void unlockAchievement(IGamesCallbacks callbacks, String achievementId, IBinder windowToken, in Bundle extraArgs) = 5023; - void incrementAchievement(IGamesCallbacks callbacks, String achievementId, int numSteps, IBinder windowToken, in Bundle extraArgs) = 5024; - void loadGame(IGamesCallbacks callbacks) = 5025; - void loadInvitations(IGamesCallbacks callbacks) = 5026; - void declineInvitation(String invitationId, int invitationType) = 5027; - void dismissInvitation(String invitationId, int invitationType) = 5028; - void createRoom(IGamesCallbacks callbacks, IBinder processBinder, int variant, in String[] invitedPlayerIds, in Bundle autoMatchCriteria, boolean enableSockets, long clientId) = 5029; - void joinRoom(IGamesCallbacks callbacks, IBinder processBinder, String matchId, boolean enableSockets, long clientId) = 5030; - void leaveRoom(IGamesCallbacks callbacks, String matchId) = 5031; - int sendReliableMessage(IGamesCallbacks callbacks, in byte[] messageData, String matchId, String recipientParticipantId) = 5032; - int sendUnreliableMessage(in byte[] messageData, String matchId, in String[] recipientParticipantIds) = 5033; - String createSocketConnection(String participantId) = 5034; - void clearNotifications(int notificationTypes) = 5035; - void loadLeaderboardsFirstParty(IGamesCallbacks callbacks, String gameId) = 5036; - void loadLeaderboardFirstParty(IGamesCallbacks callbacks, String gameId, String leaderboardId) = 5037; - void loadTopScoresFirstParty(IGamesCallbacks callbacks, String gameId, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) = 5038; - void loadPlayerCenteredScoresFirstParty(IGamesCallbacks callbacks, String gameId, String leaderboardId, int span, int leaderboardCollection, int maxResults, boolean forceReload) = 5039; - void loadAchievementsFirstParty(IGamesCallbacks callbacks, String playerId, String gameId) = 5040; - void loadGameFirstParty(IGamesCallbacks callbacks, String gameId) = 5041; - void loadGameInstancesFirstParty(IGamesCallbacks callbacks, String gameId) = 5042; - void loadGameCollectionFirstParty(IGamesCallbacks callbacks, int pageSize, int collectionType, boolean expandCachedData, boolean forceReload) = 5043; - void loadRecentlyPlayedGamesFirstParty(IGamesCallbacks callbacks, String externalPlayerId, int pageSize, boolean expandCachedData, boolean forceReload) = 5044; - void loadInvitablePlayersFirstParty(IGamesCallbacks callbacks, int pageSize, boolean expandCachedData, boolean forceReload) = 5045; - void loadRecentPlayersFirstParty(IGamesCallbacks callbacks) = 5046; - void loadCircledPlayersFirstParty(IGamesCallbacks callbacks, int pageSize, boolean expandCachedData, boolean forceReload) = 5047; - void loadSuggestedPlayersFirstParty(IGamesCallbacks callbacks) = 5048; - void dismissPlayerSuggestionFirstParty(String playerIdToDismiss) = 5049; - void declineInvitationFirstParty(String gameId, String invitationId, int invitationType) = 5050; - void loadInvitationsFirstParty(IGamesCallbacks callbacks, String gameId) = 5051; - int registerWaitingRoomListenerRestricted(IGamesCallbacks callbacks, String roomId) = 5052; - void setGameMuteStatusInternal(IGamesCallbacks callbacks, String gameId, boolean muted) = 5053; - void clearNotificationsFirstParty(String gameId, int notificationTypes) = 5054; - void loadNotifyAclInternal(IGamesCallbacks callbacks) = 5055; - void updateNotifyAclInternal(IGamesCallbacks callbacks, String aclData) = 5056; - void registerInvitationListener(IGamesCallbacks callbacks, long clientId) = 5057; - void unregisterInvitationListener(long clientId) = 5058; - int unregisterWaitingRoomListenerRestricted(String roomId) = 5059; - void isGameMutedInternal(IGamesCallbacks callbacks, String gameId) = 5060; - void loadContactSettingsInternal(IGamesCallbacks callbacks) = 5061; - void updateContactSettingsInternal(IGamesCallbacks callbacks, boolean enableMobileNotifications) = 5062; - String getSelectedAccountForGameFirstParty(String gamePackageName) = 5063; - void updateSelectedAccountForGameFirstParty(String gamePackageName, String accountName) = 5064; - Uri getGamesContentUriRestricted(String gameId) = 5065; - boolean shouldUseNewPlayerNotificationsFirstParty() = 5066; - void setUseNewPlayerNotificationsFirstParty(boolean newPlayerStyle) = 5067; - void searchForPlayersFirstParty(IGamesCallbacks callbacks, String query, int pageSize, boolean expandCachedData, boolean forceReload) = 5500; - DataHolder getCurrentGame() = 5501; -} 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 4a200050efc78c9a0f6e26c03bb962b816887416..c7c9b1c09d628073f4ea62d6cde6f1289a25edc6 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,16 +25,24 @@ import org.microg.safeparcel.SafeParceled; // TODO public class TokenRequest extends AutoSafeParcelable{ - @SafeParceled(1) - private int versionCode = 4; - @SafeParceled(3) + @Field(1) + private int versionCode = 8; + @Field(2) + private String service; + @Field(3) public String accountName; - @SafeParceled(4) + @Field(4) public Bundle extras; - @SafeParceled(9) + @Field(7) + public boolean signingIn; + @Field(9) public String consent; - @SafeParceled(15) + @Field(15) public String accountType; + @Field(16) + public int delegationType; + @Field(17) + public String delegateeUserId; public Account getAccount() { return new Account(accountName, accountType); diff --git a/play-services-api/src/main/java/com/google/android/gms/games/multiplayer/realtime/RealTimeMessage.java b/play-services-api/src/main/java/com/google/android/gms/games/multiplayer/realtime/RealTimeMessage.java deleted file mode 100644 index f6716de39ecb09e75416f4829da58ee82abffe6c..0000000000000000000000000000000000000000 --- a/play-services-api/src/main/java/com/google/android/gms/games/multiplayer/realtime/RealTimeMessage.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2013-2019 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.gms.games.multiplayer.realtime; - -import android.os.Parcel; -import android.os.Parcelable; -import android.os.Parcelable.Creator; - -public final class RealTimeMessage implements Parcelable { - public static final int RELIABLE = 1; - public static final int UNRELIABLE = 0; - - private final String mSenderParticipantId; - private final byte[] mMessageData; - private final int mIsReliable; - - public RealTimeMessage(String senderParticipantId, byte[] messageData, int isReliable) { - this.mSenderParticipantId = senderParticipantId; - this.mMessageData = messageData.clone(); - this.mIsReliable = isReliable; - } - - private RealTimeMessage(Parcel parcel) { - this(parcel.readString(), parcel.createByteArray(), parcel.readInt()); - } - - public static final Creator CREATOR = new Creator() { - @Override - public RealTimeMessage createFromParcel(Parcel in) { - return new RealTimeMessage(in); - } - @Override - public RealTimeMessage[] newArray(int size) { - return new RealTimeMessage[size]; - } - }; - - public byte[] getMessageData() { - return this.mMessageData; - } - - public String getSenderParticipantId() { - return this.mSenderParticipantId; - } - - public boolean isReliable() { - return this.mIsReliable == RELIABLE; - } - - @Override - public void writeToParcel(Parcel parcel, int flag) { - parcel.writeString(this.mSenderParticipantId); - parcel.writeByteArray(this.mMessageData); - parcel.writeInt(this.mIsReliable); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentToken.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentToken.java deleted file mode 100644 index 0d618f2f963757405515dae3f95ebf6c40d00ad7..0000000000000000000000000000000000000000 --- a/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentToken.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.google.android.gms.phenotype; - -import org.microg.safeparcel.AutoSafeParcelable; - -public class ExperimentToken extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(ExperimentToken.class); -} diff --git a/play-services-appinvite/core/src/main/kotlin/org/microg/gms/appinivite/AppInviteActivity.kt b/play-services-appinvite/core/src/main/kotlin/org/microg/gms/appinivite/AppInviteActivity.kt index a2dfff61cb4bb297634375a410db59cac3668b2a..1073d3b24b5acdc71392e2fb4a400583a72b73cb 100644 --- a/play-services-appinvite/core/src/main/kotlin/org/microg/gms/appinivite/AppInviteActivity.kt +++ b/play-services-appinvite/core/src/main/kotlin/org/microg/gms/appinivite/AppInviteActivity.kt @@ -7,14 +7,13 @@ package org.microg.gms.appinivite import android.content.Intent import android.net.Uri -import android.os.Build +import android.os.Build.VERSION.SDK_INT import android.os.Bundle import android.os.LocaleList import android.util.Log import android.view.ViewGroup import android.view.Window import android.widget.ProgressBar -import android.widget.RelativeLayout import androidx.appcompat.app.AppCompatActivity import androidx.core.content.pm.PackageInfoCompat import androidx.core.os.bundleOf @@ -146,7 +145,7 @@ class ProtobufPostRequest, O>(url: String, private val i: I, p override fun getHeaders(): Map { val headers = HashMap(super.getHeaders()) - headers["Accept-Language"] = if (Build.VERSION.SDK_INT >= 24) LocaleList.getDefault().toLanguageTags() else Locale.getDefault().language + headers["Accept-Language"] = if (SDK_INT >= 24) LocaleList.getDefault().toLanguageTags() else Locale.getDefault().language headers["X-Android-Package"] = Constants.GMS_PACKAGE_NAME headers["X-Android-Cert"] = Constants.GMS_PACKAGE_SIGNATURE_SHA1 return headers diff --git a/play-services-auth-api-phone/core/src/main/AndroidManifest.xml b/play-services-auth-api-phone/core/src/main/AndroidManifest.xml index fdf13e4544c7c774c0c617dc5131014fae79a104..21ac4f7dcd5142b85365084518c862eb8f172d82 100644 --- a/play-services-auth-api-phone/core/src/main/AndroidManifest.xml +++ b/play-services-auth-api-phone/core/src/main/AndroidManifest.xml @@ -20,7 +20,7 @@ android:name="org.microg.gms.auth.phone.AskPermissionActivity" android:exported="false" android:process=":ui" - android:theme="@style/Theme.AppCompat.DayNight.Dialog.Alert.NoActionBar" /> + android:theme="@style/Theme.Translucent" /> bundle) = 1; +} \ No newline at end of file diff --git a/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGetHubTokenCallback.aidl b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGetHubTokenCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..3c98b32dc84899ff0193093059c8854672773544 --- /dev/null +++ b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGetHubTokenCallback.aidl @@ -0,0 +1,8 @@ +package com.google.android.gms.auth.account.data; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.auth.GetHubTokenInternalResponse; + +interface IGetHubTokenCallback { + void onGetHubTokenResponse(in Status status, in GetHubTokenInternalResponse bundle) = 1; +} \ No newline at end of file diff --git a/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGetTokenWithDetailsCallback.aidl b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGetTokenWithDetailsCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..a5c4aadb1f7a291d5373ec059f0b12cb2e81179b --- /dev/null +++ b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGetTokenWithDetailsCallback.aidl @@ -0,0 +1,7 @@ +package com.google.android.gms.auth.account.data; + +import com.google.android.gms.common.api.Status; + +interface IGetTokenWithDetailsCallback { + void onTokenResults(in Status status, in Bundle bundle) = 1; +} \ No newline at end of file diff --git a/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGoogleAuthService.aidl b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGoogleAuthService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..883a6f062342b191b7a8ea483ef75a2eac5ed15a --- /dev/null +++ b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IGoogleAuthService.aidl @@ -0,0 +1,27 @@ +package com.google.android.gms.auth.account.data; + +import android.accounts.Account; + +import com.google.android.gms.auth.AccountChangeEventsRequest; +import com.google.android.gms.auth.GetAccountsRequest; +import com.google.android.gms.auth.GetHubTokenRequest; +import com.google.android.gms.auth.HasCapabilitiesRequest; +import com.google.android.gms.auth.account.data.IBundleCallback; +import com.google.android.gms.auth.account.data.IGetAccountChangeEventsCallback; +import com.google.android.gms.auth.account.data.IGetAccountsCallback; +import com.google.android.gms.auth.account.data.IGetHubTokenCallback; +import com.google.android.gms.auth.account.data.IGetTokenWithDetailsCallback; +import com.google.android.gms.auth.account.data.IHasCapabilitiesCallback; +import com.google.android.gms.auth.firstparty.dataservice.ClearTokenRequest; +import com.google.android.gms.common.api.internal.IStatusCallback; + +interface IGoogleAuthService { + void getTokenWithDetails(IGetTokenWithDetailsCallback callback, in Account account, String service, in Bundle extras) = 0; + void clearToken(IStatusCallback callback, in ClearTokenRequest request) = 1; + void requestAccountsAccess(IBundleCallback callback, String str) = 2; + void getAccountChangeEvents(IGetAccountChangeEventsCallback callback, in AccountChangeEventsRequest request) = 3; + void getAccounts(IGetAccountsCallback callback, in GetAccountsRequest request) = 4; + void removeAccount(IBundleCallback callback, in Account account) = 5; + void hasCapabilities(IHasCapabilitiesCallback callback, in HasCapabilitiesRequest request) = 6; + void getHubToken(IGetHubTokenCallback callback, in GetHubTokenRequest request) = 7; +} \ No newline at end of file diff --git a/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IHasCapabilitiesCallback.aidl b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IHasCapabilitiesCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8af4438b88fc6f50d4be469bae249535609b56fe --- /dev/null +++ b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/account/data/IHasCapabilitiesCallback.aidl @@ -0,0 +1,7 @@ +package com.google.android.gms.auth.account.data; + +import com.google.android.gms.common.api.Status; + +interface IHasCapabilitiesCallback { + void onHasCapabilities(in Status status, int mode) = 1; +} \ No newline at end of file diff --git a/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/firstparty/dataservice/ClearTokenRequest.aidl b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/firstparty/dataservice/ClearTokenRequest.aidl new file mode 100644 index 0000000000000000000000000000000000000000..2c5cf58b433311670d32478b9235057c1832a845 --- /dev/null +++ b/play-services-auth-base/src/main/aidl/com/google/android/gms/auth/firstparty/dataservice/ClearTokenRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth.firstparty.dataservice; + +parcelable ClearTokenRequest; diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java b/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetAccountsRequest.java similarity index 68% rename from play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java rename to play-services-auth-base/src/main/java/com/google/android/gms/auth/GetAccountsRequest.java index 602e150e248afad2512effd4c128d0fbc82b80b4..e39f58dd000329ea257ad90a760c5784e7756147 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/ApiClient.java +++ b/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetAccountsRequest.java @@ -14,14 +14,12 @@ * limitations under the License. */ -package org.microg.gms.common.api; +package com.google.android.gms.auth; -public interface ApiClient { - void connect(); +import org.microg.gms.common.Hide; +import org.microg.safeparcel.AutoSafeParcelable; - void disconnect(); - - boolean isConnected(); - - boolean isConnecting(); +@Hide +public class GetAccountsRequest extends AutoSafeParcelable { + public static Creator CREATOR = findCreator(GetAccountsRequest.class); } diff --git a/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java b/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java index ccecf29148cab7e857fb9fc236fbe88720b1d8bd..b7e0726f89fee51a86aba4085ef242f1ff8f7a56 100644 --- a/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java +++ b/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java @@ -8,8 +8,10 @@ package com.google.android.gms.auth; import android.accounts.Account; import android.content.Intent; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; +@Hide public class GetHubTokenInternalResponse extends AutoSafeParcelable { @Field(1) public TokenData tokenData; diff --git a/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java b/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java index cdaffb519e7c7c4badcd54bd0b777a9495471f74..9310153c61765b2c46307065c83ee35139e2f411 100644 --- a/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java +++ b/play-services-auth-base/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java @@ -5,8 +5,10 @@ package com.google.android.gms.auth; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; +@Hide public class GetHubTokenRequest extends AutoSafeParcelable { @Field(1) public String accountName; diff --git a/play-services-auth-base/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java b/play-services-auth-base/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java similarity index 58% rename from play-services-auth-base/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java rename to play-services-auth-base/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java index 27b94732cea69780e277850be6a892799c1fb6bc..2b9307f64b88c999c3f06826f7903e7c4dd51563 100644 --- a/play-services-auth-base/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java +++ b/play-services-auth-base/src/main/java/com/google/android/gms/auth/HasCapabilitiesRequest.java @@ -7,12 +7,14 @@ package com.google.android.gms.auth; import android.accounts.Account; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; -public class HasCababilitiesRequest extends AutoSafeParcelable { +@Hide +public class HasCapabilitiesRequest extends AutoSafeParcelable { @Field(1) public Account account; @Field(2) public String[] capabilities; - public static final Creator CREATOR = new AutoCreator<>(HasCababilitiesRequest.class); + public static final Creator CREATOR = new AutoCreator<>(HasCapabilitiesRequest.class); } diff --git a/play-services-auth-base/src/main/java/com/google/android/gms/auth/TokenData.java b/play-services-auth-base/src/main/java/com/google/android/gms/auth/TokenData.java index 3b7d45f531d4ff7a37d7daaa20d56ca18ec201f2..ac08cfbaefb3765522dd9a2e2f7c350c5de901ad 100644 --- a/play-services-auth-base/src/main/java/com/google/android/gms/auth/TokenData.java +++ b/play-services-auth-base/src/main/java/com/google/android/gms/auth/TokenData.java @@ -18,26 +18,29 @@ package com.google.android.gms.auth; import com.google.android.gms.common.api.Scope; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; +import java.util.ArrayList; import java.util.List; +@Hide public class TokenData extends AutoSafeParcelable { - @SafeParceled(1) + @Field(value = 1, versionCode = 1) private int versionCode = 1; - @SafeParceled(2) + @Field(2) public final String token; - @SafeParceled(3) + @Field(3) public final Long expiry; - @SafeParceled(5) + @Field(5) public final boolean isOAuth; - @SafeParceled(value = 6, subClass = Scope.class) - public final List scopes; + @Field(6) + public final List scopes; public TokenData() { token = null; @@ -50,7 +53,12 @@ public class TokenData extends AutoSafeParcelable { this.token = token; this.expiry = expiry; this.isOAuth = isOAuth; - this.scopes = scopes; + this.scopes = new ArrayList<>(); + if (scopes != null) { + for (Scope scope : scopes) { + this.scopes.add(scope.getScopeUri()); + } + } } public TokenData(String token, Long expiry) { diff --git a/play-services-auth-base/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java b/play-services-auth-base/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java index 71270ebb1357c68e497bf4a8c647a5f126a3be2d..f3a6d9809857252801acf97274b1370e54a1b1bf 100644 --- a/play-services-auth-base/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java +++ b/play-services-auth-base/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java @@ -7,8 +7,10 @@ package com.google.android.gms.auth.api.proxy; import android.os.Bundle; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; +@Hide public class ProxyRequest extends AutoSafeParcelable { public static final int HTTP_METHOD_GET = 0; public static final int HTTP_METHOD_POST = 1; diff --git a/play-services-auth-base/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java b/play-services-auth-base/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java index 951210cbbe949d4e9eb56112943c9d98ac3ac184..d7a7b9ff80b53ec49ec0cefe19d8aa3ee6542603 100644 --- a/play-services-auth-base/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java +++ b/play-services-auth-base/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java @@ -8,8 +8,10 @@ package com.google.android.gms.auth.api.proxy; import android.app.PendingIntent; import android.os.Bundle; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; +@Hide public class ProxyResponse extends AutoSafeParcelable { public static final int STATUS_CODE_NO_CONNECTION = -1; diff --git a/play-services-auth-base/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ClearTokenRequest.java b/play-services-auth-base/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ClearTokenRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..3e13e2aeb7572963eecb63dd21f804b2d7228380 --- /dev/null +++ b/play-services-auth-base/src/main/java/com/google/android/gms/auth/firstparty/dataservice/ClearTokenRequest.java @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.firstparty.dataservice; + +import org.microg.gms.common.Hide; +import org.microg.safeparcel.AutoSafeParcelable; + +@Hide +public class ClearTokenRequest extends AutoSafeParcelable { + @Field(1) + private int versionCode = 1; + @Field(2) + public String token; + + public static final Creator CREATOR = findCreator(ClearTokenRequest.class); +} diff --git a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/signin/internal/ISignInCallbacks.aidl b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/signin/internal/ISignInCallbacks.aidl index 9884a73afeb75e3961cc8eb11cb52fb897e41e78..1b54d1f17d63212ed184062de83a00b99d14217a 100644 --- a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/signin/internal/ISignInCallbacks.aidl +++ b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/signin/internal/ISignInCallbacks.aidl @@ -4,7 +4,7 @@ import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.common.api.Status; interface ISignInCallbacks { - void onSignIn(in GoogleSignInAccount callbacks, in Status status) = 100; + void onSignIn(in GoogleSignInAccount account, in Status status) = 100; void onSignOut(in Status status) = 101; void onRevokeAccess(in Status status) = 102; } \ No newline at end of file diff --git a/play-services-auth/src/main/java/com/google/android/gms/auth/api/signin/SignInAccount.java b/play-services-auth/src/main/java/com/google/android/gms/auth/api/signin/SignInAccount.java new file mode 100644 index 0000000000000000000000000000000000000000..b49a17e93317eb62131d7d76cf2a27e5fc9e0d2b --- /dev/null +++ b/play-services-auth/src/main/java/com/google/android/gms/auth/api/signin/SignInAccount.java @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.signin; + +import androidx.annotation.NonNull; +import org.microg.gms.common.Hide; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +@Hide +public class SignInAccount extends AutoSafeParcelable { + @Field(4) + public String email; + @Field(7) + public GoogleSignInAccount googleSignInAccount; + @Field(8) + public String userId; + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("SignInAccount").field("email", email).field("account", googleSignInAccount).field("userId", userId).end(); + } + + public static final Creator CREATOR = findCreator(SignInAccount.class); +} diff --git a/play-services-auth/src/main/java/com/google/android/gms/auth/api/signin/internal/SignInConfiguration.java b/play-services-auth/src/main/java/com/google/android/gms/auth/api/signin/internal/SignInConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..7ec353425d671ad68088a817f29995d99b91639a --- /dev/null +++ b/play-services-auth/src/main/java/com/google/android/gms/auth/api/signin/internal/SignInConfiguration.java @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.signin.internal; + +import androidx.annotation.NonNull; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import org.microg.gms.common.Hide; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +@Hide +public class SignInConfiguration extends AutoSafeParcelable { + @Field(2) + public String packageName; + @Field(5) + public GoogleSignInOptions options; + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("SignInConfiguration").field("packageName", packageName).field("options", options).end(); + } + + public static final Creator CREATOR = findCreator(SignInConfiguration.class); +} diff --git a/play-services-base/build.gradle b/play-services-base/build.gradle index 049c754b6b08dff095fb3a0be3d7bffe0b26ba6a..0faf316fd400860429bddff8bc8ae6b9a573c27e 100644 --- a/play-services-base/build.gradle +++ b/play-services-base/build.gradle @@ -56,4 +56,6 @@ dependencies { api "androidx.fragment:fragment:1.0.0" api project(':play-services-basement') api project(':play-services-tasks') + + annotationProcessor project(':safe-parcel-processor') } 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 da1def3edac964a40d1774edca7ac6bbffe3b739..cfcce5dba69ec5de44079ae9fde3c1f97778bb76 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 @@ -24,10 +24,7 @@ import android.os.RemoteException; import android.util.Log; import com.google.android.gms.common.api.Scope; -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 com.google.android.gms.common.internal.ValidateAccountRequest; +import com.google.android.gms.common.internal.*; import org.microg.gms.common.GmsService; @@ -108,7 +105,7 @@ public abstract class AbstractGmsServiceBroker extends IGmsServiceBroker.Stub { Bundle extras = params == null ? new Bundle() : params; extras.putString("com.google.android.gms.games.key.gamePackageName", gamePackageName); extras.putString("com.google.android.gms.games.key.desiredLocale", desiredLocale); - //extras.putParcelable("com.google.android.gms.games.key.popupWindowToken", popupWindowToken); + extras.putParcelable("com.google.android.gms.games.key.popupWindowToken", new BinderWrapper(popupWindowToken)); callGetService(GmsService.GAMES, callback, versionCode, packageName, extras, accountName, scopes); } 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 f30fdf3aa83dc314073ade9262e09c0f9c4740d2..949f6401bf5af3766c39a141a90a490226d5eb4b 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 @@ -76,6 +76,7 @@ public class PackageUtils { KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.walletnfcrel", "82759e2db43f9ccbafce313bc674f35748fabd7a"); KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.recorder", "394d84cd2cf89d3453702c663f98ec6554afc3cd"); KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.messaging", "0980a12be993528c19107bc21ad811478c63cefc"); + KNOWN_GOOGLE_PACKAGES.put("com.google.android.apps.tachyon", "a0bc09af527b6397c7a9ef171d6cf76f757becc3"); } public static boolean isGooglePackage(Context context, String packageName) { diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt new file mode 100644 index 0000000000000000000000000000000000000000..1e21f6d9b571109917966cd2dd2ab6c42502b739 --- /dev/null +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/MetaDataPreferences.kt @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.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/gms/settings/SettingsProvider.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index 0ff85092b9b5cd0a81bc33b2fea7e275ecda211f..0e2948a90b98ea2e717459bed6737986512dbae3 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 @@ -27,6 +27,8 @@ import org.microg.gms.settings.SettingsContract.SafetyNet import org.microg.gms.settings.SettingsContract.getAuthority import java.io.File +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. @@ -53,6 +55,9 @@ class SettingsProvider : ContentProvider() { null } } + private val metaDataPreferences: SharedPreferences by lazy { + MetaDataPreferences(context!!, SETTINGS_PREFIX) + } override fun onCreate(): Boolean { return true @@ -355,12 +360,12 @@ 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) } - private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences).getString(key, def) - private fun getSettingsInt(key: String, def: Int): Int = listOf(preferences, systemDefaultPreferences).getInt(key, def) - private fun getSettingsLong(key: String, def: Long): Long = listOf(preferences, systemDefaultPreferences).getLong(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) private fun getUnifiedNlpSettingsStringSetCompat(key: String, def: Set): Set = listOf(unifiedNlpPreferences, preferences, systemDefaultPreferences).getStringSetCompat(key, def) private fun SharedPreferences.getStringSetCompat(key: String, def: Set): Set { diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/ui/settings/SettingsProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..ecebb042163d414f99c2997d02d38497a14f8782 --- /dev/null +++ b/play-services-base/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-base/core/src/main/res/values-be/strings.xml b/play-services-base/core/src/main/res/values-be/strings.xml index 7420ee8a05624e32810531f6fd85a7cec11c72c1..c019c30e4ad078a0ea65108bc89a1a366a897fd8 100644 --- a/play-services-base/core/src/main/res/values-be/strings.xml +++ b/play-services-base/core/src/main/res/values-be/strings.xml @@ -15,4 +15,11 @@ Паказаць усё Aдкрыць + + Выключана + Уключана + Аўтаматычна + Уручную + Укл. + Выкл. diff --git a/play-services-base/core/src/main/res/values-de/strings.xml b/play-services-base/core/src/main/res/values-de/strings.xml index c353f0e9b4a8febeb0a08ddd6c8f75dc59765042..1eaffe2dc3dd4bb6185e0e4b6267ada68c33a722 100644 --- a/play-services-base/core/src/main/res/values-de/strings.xml +++ b/play-services-base/core/src/main/res/values-de/strings.xml @@ -15,4 +15,11 @@ Alle anzeigen Öffnen + + Deaktiviert + Aktiviert + Automatisch + Manuell + Ein + Aus diff --git a/play-services-base/core/src/main/res/values-es/strings.xml b/play-services-base/core/src/main/res/values-es/strings.xml index a380438a03ccf6d56f6530c7763acaa3a1781a65..91870f939b1529c846e16a3a2d0340eb148158e9 100644 --- a/play-services-base/core/src/main/res/values-es/strings.xml +++ b/play-services-base/core/src/main/res/values-es/strings.xml @@ -11,4 +11,11 @@ Ver todo Abrir + + Desactivado + Activado + Automático + Manual + Encendido + Apagado diff --git a/play-services-base/core/src/main/res/values-fr/strings.xml b/play-services-base/core/src/main/res/values-fr/strings.xml index 15d20d0702a3f819b43f4c46bc019405c36715ce..e25c1caa7ced43157f2ce49c5eecb5acc4a7e856 100644 --- a/play-services-base/core/src/main/res/values-fr/strings.xml +++ b/play-services-base/core/src/main/res/values-fr/strings.xml @@ -8,4 +8,9 @@ Avancé Aucun + + Désactivé + Activé + Automatique + Manuel diff --git a/play-services-base/core/src/main/res/values-it/strings.xml b/play-services-base/core/src/main/res/values-it/strings.xml index 189a2e1de9b4b0017bf501a0990b68003b5fd5af..216c54e3a0e75376e3db734e1c999a3a91a4074b 100644 --- a/play-services-base/core/src/main/res/values-it/strings.xml +++ b/play-services-base/core/src/main/res/values-it/strings.xml @@ -9,4 +9,11 @@ Nessuna Mostra tutte Apri + + Disabilitato + Abilitato + Automatico + Manuale + Abilitato + Disabilitato 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 8f056e66db373f161718db6bdd1b0fd307d3baaf..0a037fefbcff2487f914ebebe6f66b2303bac3fa 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 @@ -11,4 +11,11 @@ 全て表示 開く + + 無効 + 有効 + 自動 + 手動 + On + Off diff --git a/play-services-base/core/src/main/res/values-pl/strings.xml b/play-services-base/core/src/main/res/values-pl/strings.xml index 2af5a5ae1625a1480790a8a9a3a4590e1bdb7793..dc8dfe74e028f93d1971eea4db6c8ffb059a36a4 100644 --- a/play-services-base/core/src/main/res/values-pl/strings.xml +++ b/play-services-base/core/src/main/res/values-pl/strings.xml @@ -8,4 +8,9 @@ Zaawansowane Brak + + Nieaktywny + Aktywny + Automatyczny + Ręczny diff --git a/play-services-base/core/src/main/res/values-ru/strings.xml b/play-services-base/core/src/main/res/values-ru/strings.xml index 69dc64fdd3ca398d5ebf0ceffc954d350b0259a0..bea92fde0749215a0670170a510e82af1d4bba54 100644 --- a/play-services-base/core/src/main/res/values-ru/strings.xml +++ b/play-services-base/core/src/main/res/values-ru/strings.xml @@ -15,4 +15,11 @@ Показать всё Открыть + + Выключено + Включено + Автоматически + Вручную + Вкл. + Выкл. diff --git a/play-services-base/core/src/main/res/values-uk/strings.xml b/play-services-base/core/src/main/res/values-uk/strings.xml index ad2ff3d1533b9a583f1387e09d69050826c707b4..5da45c35dd8d2378719fee9d83d40fe1d865128b 100644 --- a/play-services-base/core/src/main/res/values-uk/strings.xml +++ b/play-services-base/core/src/main/res/values-uk/strings.xml @@ -8,4 +8,9 @@ Додатково Порожньо + + Вимкнуто + Увімкнуто + Автоматично + Вручну diff --git a/play-services-base/core/src/main/res/values-zh-rCN/strings.xml b/play-services-base/core/src/main/res/values-zh-rCN/strings.xml index 6916323f7be18f2486f4563cbe075e226a7aad7f..71ec792476e8ea97bfba6441605337abcb6c0c40 100644 --- a/play-services-base/core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-base/core/src/main/res/values-zh-rCN/strings.xml @@ -8,4 +8,11 @@ 全部显示 打开 + + 已禁用 + 已启用 + 自动 + 手动 + + diff --git a/play-services-base/core/src/main/res/values/strings.xml b/play-services-base/core/src/main/res/values/strings.xml index 911bc437573ff96ff080eef9dd808cf4e397c0aa..a51ab4587a8c1624689462b57379a5d937303656 100644 --- a/play-services-base/core/src/main/res/values/strings.xml +++ b/play-services-base/core/src/main/res/values/strings.xml @@ -15,4 +15,11 @@ See all Open + + Disabled + Enabled + Automatic + Manual + On + Off diff --git a/play-services-base/core/src/main/res/values/themes.xml b/play-services-base/core/src/main/res/values/themes.xml index 686ba6d625739ea7e13ac764b2ae4c21cba46973..8b56232f8639e875d00e4ab8a54a6d9edda4ba47 100644 --- a/play-services-base/core/src/main/res/values/themes.xml +++ b/play-services-base/core/src/main/res/values/themes.xml @@ -24,4 +24,15 @@ false true + + diff --git a/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/AuthAccountResult.aidl b/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/AuthAccountResult.aidl new file mode 100644 index 0000000000000000000000000000000000000000..77d8774b6bfffa33151baff7992b208ef2363f69 --- /dev/null +++ b/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/AuthAccountResult.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.signin.internal; + +parcelable AuthAccountResult; \ No newline at end of file diff --git a/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/ISignInCallbacks.aidl b/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/ISignInCallbacks.aidl index 16d2471f1dba46aff89ba0266d0f19002aa76cc4..3f54a0c3acc6fcb304e513d34cbcd3931a16795c 100644 --- a/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/ISignInCallbacks.aidl +++ b/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/ISignInCallbacks.aidl @@ -1,7 +1,17 @@ package com.google.android.gms.signin.internal; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.signin.internal.AuthAccountResult; +import com.google.android.gms.signin.internal.RecordConsentByConsentResultResponse; import com.google.android.gms.signin.internal.SignInResponse; interface ISignInCallbacks { + void onAuthAccount(in ConnectionResult connectionResult, in AuthAccountResult result) = 2; + void onPutAccount(in Status status) = 3; + void onRecordConsent(in Status status) = 5; + void onCurrentAccount(in Status status, in GoogleSignInAccount account) = 6; void onSignIn(in SignInResponse response) = 7; + void onRecordConsentByConsent(in RecordConsentByConsentResultResponse response) = 8; } \ No newline at end of file diff --git a/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/RecordConsentByConsentResultResponse.aidl b/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/RecordConsentByConsentResultResponse.aidl new file mode 100644 index 0000000000000000000000000000000000000000..097df34b81d171edc0957cbf8fb0076fe5541f12 --- /dev/null +++ b/play-services-base/src/main/aidl/com/google/android/gms/signin/internal/RecordConsentByConsentResultResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.signin.internal; + +parcelable RecordConsentByConsentResultResponse; \ No newline at end of file diff --git a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInAccount.java b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInAccount.java index fb3a505ea7f5a98a89ea7f45650a8edfdc72976e..e28eb13ba2aeb3629d4f25aa3c765d7c2cc283ec 100644 --- a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInAccount.java +++ b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInAccount.java @@ -13,8 +13,12 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.gms.common.api.Scope; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.microg.gms.auth.AuthConstants; import org.microg.gms.common.Hide; +import org.microg.gms.utils.ToStringHelper; import org.microg.safeparcel.AutoSafeParcelable; import java.util.ArrayList; @@ -61,11 +65,18 @@ public class GoogleSignInAccount extends AutoSafeParcelable { } @Hide - public GoogleSignInAccount(Account account, Set grantedScopes) { - this.email = account.name; - this.obfuscatedIdentifier = account.name; - this.expirationTime = 0; + public GoogleSignInAccount(@Nullable String id, @Nullable String tokenId, @Nullable String email, @Nullable String displayName, @Nullable Uri photoUrl, String serverAuthCode, long expirationTime, String obfuscatedIdentifier, Set grantedScopes, @Nullable String givenName, @Nullable String familyName) { + this.id = id; + this.tokenId = tokenId; + this.email = email; + this.displayName = displayName; + this.photoUrl = photoUrl; + this.serverAuthCode = serverAuthCode; + this.expirationTime = expirationTime; + this.obfuscatedIdentifier = obfuscatedIdentifier; this.grantedScopes = new ArrayList<>(grantedScopes); + this.givenName = givenName; + this.familyName = familyName; } /** @@ -179,10 +190,93 @@ public class GoogleSignInAccount extends AutoSafeParcelable { return ((GoogleSignInAccount) obj).obfuscatedIdentifier.equals(obfuscatedIdentifier) && ((GoogleSignInAccount) obj).getGrantedScopes().equals(getGrantedScopes()); } + @Hide + public String getObfuscatedIdentifier() { + return obfuscatedIdentifier; + } + + private static final String JSON_ID = "id"; + private static final String JSON_TOKEN_ID = "tokenId"; + private static final String JSON_EMAIL = "email"; + private static final String JSON_DISPLAY_NAME = "displayName"; + private static final String JSON_GIVEN_NAME = "givenName"; + private static final String JSON_FAMILY_NAME = "familyName"; + private static final String JSON_PHOTO_URL = "photoUrl"; + private static final String JSON_SERVER_AUTH_CODE = "serverAuthCode"; + private static final String JSON_EXPIRATION_TIME = "expirationTime"; + private static final String JSON_OBFUSCATED_IDENTIFIER = "obfuscatedIdentifier"; + private static final String JSON_GRANTED_SCOPES = "grantedScopes"; + + @Hide + public static GoogleSignInAccount fromJson(@Nullable String jsonString) throws JSONException { + if (jsonString == null) return null; + JSONObject json = new JSONObject(jsonString); + GoogleSignInAccount account = new GoogleSignInAccount(); + account.id = json.optString(JSON_ID); + account.tokenId = json.has(JSON_TOKEN_ID) ? json.optString(JSON_TOKEN_ID) : null; + account.email = json.has(JSON_EMAIL) ? json.optString(JSON_EMAIL) : null; + account.displayName = json.has(JSON_DISPLAY_NAME) ? json.optString(JSON_DISPLAY_NAME) : null; + account.givenName = json.has(JSON_GIVEN_NAME) ? json.optString(JSON_GIVEN_NAME) : null; + account.familyName = json.has(JSON_FAMILY_NAME) ? json.optString(JSON_FAMILY_NAME) : null; + account.photoUrl = json.has(JSON_PHOTO_URL) ? Uri.parse(json.optString(JSON_PHOTO_URL)) : null; + account.serverAuthCode = json.has(JSON_SERVER_AUTH_CODE) ? json.optString(JSON_SERVER_AUTH_CODE) : null; + account.expirationTime = Long.parseLong(json.getString(JSON_EXPIRATION_TIME)); + account.obfuscatedIdentifier = json.getString(JSON_OBFUSCATED_IDENTIFIER); + account.grantedScopes = new ArrayList<>(); + JSONArray jsonGrantedScopes = json.getJSONArray(JSON_GRANTED_SCOPES); + for (int i = 0; i < jsonGrantedScopes.length(); i++) { + account.grantedScopes.add(new Scope(jsonGrantedScopes.getString(i))); + } + return account; + } + + @Hide + @NonNull + public String toJson() { + JSONObject json = new JSONObject(); + try { + if (id != null) json.put(JSON_ID, id); + if (tokenId != null) json.put(JSON_TOKEN_ID, tokenId); + if (email != null) json.put(JSON_EMAIL, email); + if (displayName != null) json.put(JSON_DISPLAY_NAME, displayName); + if (givenName != null) json.put(JSON_GIVEN_NAME, givenName); + if (familyName != null) json.put(JSON_FAMILY_NAME, familyName); + if (photoUrl != null) json.put(JSON_PHOTO_URL, photoUrl.toString()); + if (serverAuthCode != null) json.put(JSON_SERVER_AUTH_CODE, serverAuthCode); + json.put(JSON_EXPIRATION_TIME, expirationTime); + json.put(JSON_OBFUSCATED_IDENTIFIER, obfuscatedIdentifier); + JSONArray jsonGrantedScopes = new JSONArray(); + for (Scope grantedScope : grantedScopes) { + jsonGrantedScopes.put(grantedScope.getScopeUri()); + } + json.put(JSON_GRANTED_SCOPES, jsonGrantedScopes); + return json.toString(); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + @Override public int hashCode() { return (obfuscatedIdentifier.hashCode() + 527) * 31 + getGrantedScopes().hashCode(); } + @NonNull + @Override + public String toString() { + return ToStringHelper.name("GoogleSignInAccount") + .field("id", id) + .field("tokenId", tokenId) + .field("email", email) + .field("displayName", displayName) + .field("givenName", givenName) + .field("familyName", familyName) + .field("photoUrl", photoUrl) + .field("serverAuthCode", serverAuthCode) + .field("expirationTime", expirationTime) + .field("obfuscatedIdentifier", obfuscatedIdentifier) + .end(); + } + public static final Creator CREATOR = new AutoCreator<>(GoogleSignInAccount.class); } diff --git a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java index a16f141234e02eb486ccd563086b126de1ba866b..6033bdbbaaba8c47ffdfc0b74a051d4933c55953 100644 --- a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java +++ b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java @@ -11,15 +11,18 @@ package com.google.android.gms.auth.api.signin; import android.accounts.Account; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.google.android.gms.auth.api.signin.internal.GoogleSignInOptionsExtensionParcelable; import com.google.android.gms.common.Scopes; import com.google.android.gms.common.api.Scope; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.microg.gms.auth.AuthConstants; +import org.microg.gms.common.Hide; +import org.microg.gms.utils.ToStringHelper; import org.microg.safeparcel.AutoSafeParcelable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** * {@code GoogleSignInOptions} contains options used to configure the {@link Auth#GOOGLE_SIGN_IN_API}. @@ -37,7 +40,7 @@ public class GoogleSignInOptions extends AutoSafeParcelable { * NOT use {@link GoogleSignInOptions.Builder#requestIdToken(String)} to request user's real Google identity assertion. */ @NonNull - public static final GoogleSignInOptions DEFAULT_GAMES_SIGN_IN = null; + public static final GoogleSignInOptions DEFAULT_GAMES_SIGN_IN = new Builder().requestScopes(new Scope(Scopes.GAMES_LITE)).build(); /** * Default configuration for Google Sign In. You can get a stable user ID and basic profile info back via {@link GoogleSignInAccount#getId()} after you @@ -45,7 +48,7 @@ public class GoogleSignInOptions extends AutoSafeParcelable { * sign in result, please build a configuration via {@code new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)}. */ @NonNull - public static final GoogleSignInOptions DEFAULT_SIGN_IN = null; + public static final GoogleSignInOptions DEFAULT_SIGN_IN = new Builder().requestId().requestProfile().build(); @Field(1) private int versionCode = 3; @@ -63,8 +66,8 @@ public class GoogleSignInOptions extends AutoSafeParcelable { private String serverClientId; @Field(8) private String hostedDomain; - // @Field(9) -// private ArrayList extensions; + @Field(9) + private ArrayList extensions; @Field(10) private String logSessionId; @@ -78,7 +81,52 @@ public class GoogleSignInOptions extends AutoSafeParcelable { */ @NonNull public Scope[] getScopeArray() { - return null; + return scopes.toArray(new Scope[0]); + } + + @Hide + public List getScopes() { + return Collections.unmodifiableList(scopes); + } + + @Hide + public Account getAccount() { + return account; + } + + @Hide + public boolean isIdTokenRequested() { + return idTokenRequested; + } + + @Hide + public boolean isServerAuthCodeRequested() { + return serverAuthCodeRequested; + } + + @Hide + public boolean isForceCodeForRefreshToken() { + return forceCodeForRefreshToken; + } + + @Hide + public String getServerClientId() { + return serverClientId; + } + + @Hide + public String getHostedDomain() { + return hostedDomain; + } + + @Hide + public List getExtensions() { + return Collections.unmodifiableList(extensions); + } + + @Hide + public String getLogSessionId() { + return logSessionId; } /** @@ -95,6 +143,7 @@ public class GoogleSignInOptions extends AutoSafeParcelable { private Account account; @Nullable private String hostedDomain; + private final Map extensionMap = new HashMap<>(); public Builder() { this.scopes = new HashSet<>(); @@ -108,6 +157,9 @@ public class GoogleSignInOptions extends AutoSafeParcelable { this.serverClientId = options.serverClientId; this.account = options.account; this.hostedDomain = options.hostedDomain; + for (GoogleSignInOptionsExtensionParcelable extension : options.extensions) { + extensionMap.put(extension.type, extension); + } } /** @@ -117,6 +169,14 @@ public class GoogleSignInOptions extends AutoSafeParcelable { */ @NonNull public Builder addExtension(GoogleSignInOptionsExtension extension) { + if (this.extensionMap.containsKey(extension.getExtensionType())) { + throw new IllegalStateException("Only one extension per type may be added"); + } + List scopes = extension.getImpliedScopes(); + if (scopes != null) { + this.scopes.addAll(scopes); + } + this.extensionMap.put(extension.getExtensionType(), new GoogleSignInOptionsExtensionParcelable(extension)); return this; } @@ -247,9 +307,70 @@ public class GoogleSignInOptions extends AutoSafeParcelable { options.serverClientId = serverClientId; options.account = account; options.hostedDomain = hostedDomain; + options.extensions = new ArrayList<>(extensionMap.values()); return options; } } - public static final Creator CREATOR = new AutoCreator<>(GoogleSignInOptions.class); + private static final String JSON_SCOPES = "scopes"; + private static final String JSON_ACCOUNT_NAME = "accountName"; + private static final String JSON_ID_TOKEN_REQUESTED = "idTokenRequested"; + private static final String JSON_FORCE_CODE_FOR_REFRESH_TOKEN = "forceCodeForRefreshToken"; + private static final String JSON_SERVER_AUTH_REQUESTED = "serverAuthRequested"; + private static final String JSON_SERVER_CLIENT_ID = "serverClientId"; + private static final String JSON_HOSTED_DOMAIN = "hostedDomain"; + + public static GoogleSignInOptions fromJson(String jsonString) throws JSONException { + if (jsonString == null) return null; + JSONObject json = new JSONObject(jsonString); + GoogleSignInOptions options = new GoogleSignInOptions(); + JSONArray jsonScopes = json.getJSONArray(JSON_SCOPES); + for (int i = 0; i < jsonScopes.length(); i++) { + options.scopes.add(new Scope(jsonScopes.getString(i))); + } + options.account = json.has(JSON_ACCOUNT_NAME) ? new Account(json.optString(JSON_ACCOUNT_NAME), AuthConstants.DEFAULT_ACCOUNT_TYPE) : null; + options.idTokenRequested = json.getBoolean(JSON_ID_TOKEN_REQUESTED); + options.forceCodeForRefreshToken = json.getBoolean(JSON_FORCE_CODE_FOR_REFRESH_TOKEN); + options.serverAuthCodeRequested = json.getBoolean(JSON_SERVER_AUTH_REQUESTED); + options.serverClientId = json.has(JSON_SERVER_CLIENT_ID) ? json.optString(JSON_SERVER_CLIENT_ID) : null; + options.hostedDomain = json.has(JSON_HOSTED_DOMAIN) ? json.optString(JSON_HOSTED_DOMAIN) : null; + return options; + } + + @NonNull + public String toJson() { + JSONObject json = new JSONObject(); + try { + JSONArray jsonScopes = new JSONArray(); + for (Scope scope : scopes) { + jsonScopes.put(scope.getScopeUri()); + } + json.put(JSON_SCOPES, jsonScopes); + if (account != null) json.put(JSON_ACCOUNT_NAME, account.name); + json.put(JSON_ID_TOKEN_REQUESTED, idTokenRequested); + json.put(JSON_FORCE_CODE_FOR_REFRESH_TOKEN, forceCodeForRefreshToken); + json.put(JSON_SERVER_AUTH_REQUESTED, serverAuthCodeRequested); + if (serverClientId != null) json.put(JSON_SERVER_CLIENT_ID, serverClientId); + if (hostedDomain != null) json.put(JSON_HOSTED_DOMAIN, hostedDomain); + return json.toString(); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("GoogleSignInOptions") + .field("scopes", scopes) + .field("account", account) + .field("idTokenRequested", idTokenRequested) + .field("forceCodeForRefreshToken", forceCodeForRefreshToken) + .field("serverAuthCodeRequested", serverAuthCodeRequested) + .field("serverClientId", serverClientId) + .field("hostedDomain", hostedDomain) + .end(); + } + + public static final Creator CREATOR = findCreator(GoogleSignInOptions.class); } diff --git a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptionsExtension.java b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptionsExtension.java index 82a648ba357c76e82c9c866ea49348b4aefaccf6..6f8d661e356156527915ab547e6cdc443229cc17 100644 --- a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptionsExtension.java +++ b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptionsExtension.java @@ -8,10 +8,29 @@ package com.google.android.gms.auth.api.signin; +import android.os.Bundle; +import com.google.android.gms.common.api.Scope; +import org.microg.gms.common.Hide; + +import java.util.List; + /** * An interface for API specific extension for {@link GoogleSignInOptions}. * * @see GoogleSignInOptions.Builder#addExtension(GoogleSignInOptionsExtension). */ public interface GoogleSignInOptionsExtension { + @Hide + int GAMES = 1; + @Hide + int FITNESS = 3; + + @Hide + int getExtensionType(); + + @Hide + Bundle toBundle(); + + @Hide + List getImpliedScopes(); } diff --git a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/internal/GoogleSignInOptionsExtensionParcelable.java b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/internal/GoogleSignInOptionsExtensionParcelable.java new file mode 100644 index 0000000000000000000000000000000000000000..ead1e437f1ea72e2bf5cc0745db368941bfb191e --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/internal/GoogleSignInOptionsExtensionParcelable.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.signin.internal; + +import android.os.Bundle; +import android.os.Parcel; +import androidx.annotation.NonNull; +import com.google.android.gms.auth.api.signin.GoogleSignInOptionsExtension; +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 org.microg.gms.common.Hide; + +@Hide +@SafeParcelable.Class +public class GoogleSignInOptionsExtensionParcelable extends AbstractSafeParcelable { + @Field(1) + public final int versionCode; + @Field(2) + public final int type; + @Field(3) + public final Bundle bundle; + + public GoogleSignInOptionsExtensionParcelable(GoogleSignInOptionsExtension extension) { + this(extension.getExtensionType(), extension.toBundle()); + } + + public GoogleSignInOptionsExtensionParcelable(int type, Bundle bundle) { + this(1, type, bundle); + } + + @Constructor + public GoogleSignInOptionsExtensionParcelable(@Param(1) int versionCode, @Param(2) int type, @Param(3) Bundle bundle) { + this.versionCode = versionCode; + this.type = type; + this.bundle = bundle; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GoogleSignInOptionsExtensionParcelable.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 065fff8ae872c773d3a18c964c8b7116e1bb1552..fd91a375d5ba18dc908000f00f81db0f0272ba87 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 @@ -20,6 +20,7 @@ import android.accounts.Account; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import org.microg.gms.common.Hide; import org.microg.gms.common.PublicApi; import org.microg.gms.common.api.ApiClientBuilder; @@ -100,4 +101,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 2cb5e7834127a682c60ea20d492b748860a1d85f..4a33737cfd2ad888c65debcec98efe840291d14d 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 @@ -13,7 +13,6 @@ import com.google.android.gms.tasks.TaskCompletionSource; import org.microg.gms.common.Hide; import org.microg.gms.common.PublicApi; -import org.microg.gms.common.api.ApiClient; import org.microg.gms.common.api.GoogleApiManager; import org.microg.gms.common.api.PendingGoogleApiCall; @@ -39,7 +38,7 @@ public abstract class GoogleApi implements HasApiKey Task scheduleTask(PendingGoogleApiCall apiCall) { + protected Task scheduleTask(PendingGoogleApiCall apiCall) { TaskCompletionSource completionSource = new TaskCompletionSource<>(); manager.scheduleTask(this, apiCall, completionSource); return completionSource.getTask(); 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 f74e53045b0fccf46684ca94074d4fc5a18dcde0..62e52ba36232e0237185b6f5fbb06dd255e94f49 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 @@ -364,7 +364,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; } public Builder enableAutoManage(FragmentActivity fragmentActivity, int cliendId, diff --git a/play-services-base/src/main/java/com/google/android/gms/common/data/AbstractDataBuffer.java b/play-services-base/src/main/java/com/google/android/gms/common/data/AbstractDataBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..211aee1db3b9873abe6ba4f65752960fdeceb623 --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/common/data/AbstractDataBuffer.java @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2023 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.data; + +import org.microg.gms.common.Hide; + +import java.util.Iterator; + +/** + * Default implementation of DataBuffer. An {@code AbstractDataBuffer} wraps data provided across the binder from Google Play services. + */ +public abstract class AbstractDataBuffer implements DataBuffer { + protected final DataHolder dataHolder; + + @Hide + public AbstractDataBuffer(DataHolder dataHolder) { + this.dataHolder = dataHolder; + } + + /** + * Releases the data buffer, for use in try-with-resources. + *

+ * Both close and release shall have identical semantics, and are idempotent. + */ + @Override + public void close() { + 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. In other words: + *

+ * {@code buffer.get(i) == buffer.get(i)} may return false. + *

+ * {@code buffer.get(i).equals(buffer.get(i))} will return true. + * + * @param position The position of the item to retrieve. + * @return the item at {@code position} in this buffer. + */ + public abstract T get(int position); + + @Override + public int getCount() { + if (dataHolder == null) return 0; + return dataHolder.getCount(); + } + + /** + * @deprecated {@link #release()} and {@link #close()} are idempotent, and so is safe to call multiple times + */ + @Deprecated + @Override + public boolean isClosed() { + if (dataHolder == null) return true; + return dataHolder.isClosed(); + } + + @Override + public Iterator iterator() { + return new DataBufferIterator(this); + } + + /** + * Releases resources used by the buffer. This method is idempotent. + */ + @Override + public void release() { + if (dataHolder != null) dataHolder.close(); + } + + /** + * In order to use this you should correctly override DataBufferRef.setDataRow(int) in your DataBufferRef implementation. + * Be careful: there will be single DataBufferRef while iterating. If you are not sure - DO NOT USE this iterator. + * + * @see SingleRefDataBufferIterator + */ + @Override + public Iterator singleRefIterator() { + return new SingleRefDataBufferIterator(this); + } +} 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 309a667bec53a994ef02e5dd0e9839dbd2c8b6f4..e502c8f0a64627b5fd6ee132a98a10d9770b3a05 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 @@ -1,17 +1,9 @@ /* - * 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. + * SPDX-FileCopyrightText: 2015 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.data; @@ -23,67 +15,43 @@ import org.microg.gms.common.PublicApi; import java.util.Iterator; /** - * TODO + * Interface for a buffer of typed data. */ -@PublicApi -public abstract class DataBuffer implements Releasable, Iterable { - - private DataHolder dataHolder; - - @PublicApi(exclude = true) - public DataBuffer(DataHolder dataHolder) { - this.dataHolder = dataHolder; - } +public interface DataBuffer extends Releasable, Iterable { /** - * @deprecated use {@link #release()} instead + * Releases the data buffer, for use in try-with-resources. + *

+ * Both close and release shall have identical semantics, and are idempotent. */ - @Deprecated - public final void close() { - release(); - } + void close(); /** - * 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. + * Returns an element on specified position. */ - public abstract T get(int position); + T get(int position); - public int getCount() { - return dataHolder == null ? 0 : dataHolder.getCount(); - } + int getCount(); /** * @deprecated {@link #release()} is idempotent, and so is safe to call multiple times */ @Deprecated - public boolean isClosed() { - return false; - } + boolean isClosed(); @Override - public Iterator iterator() { - return null; - } + Iterator iterator(); /** * Releases resources used by the buffer. This method is idempotent. */ @Override - public void release() { - - } + void release(); /** * 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; - } + Iterator singleRefIterator(); } diff --git a/play-services-base/src/main/java/com/google/android/gms/common/data/DataBufferIterator.java b/play-services-base/src/main/java/com/google/android/gms/common/data/DataBufferIterator.java new file mode 100644 index 0000000000000000000000000000000000000000..f12180d20b97064209777dd921563282c390922d --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/common/data/DataBufferIterator.java @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.data; + +import androidx.annotation.NonNull; +import org.microg.gms.common.Hide; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +@Hide +public class DataBufferIterator implements Iterator { + protected DataBuffer dataBuffer; + protected int position = -1; + + public DataBufferIterator(@NonNull DataBuffer dataBuffer) { + this.dataBuffer = dataBuffer; + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("Cannot advance the iterator beyond " + position); + } + return dataBuffer.get(++position); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Cannot remove elements from a DataBufferIterator"); + } + + @Override + public boolean hasNext() { + return this.position < this.dataBuffer.getCount() - 1; + } +} diff --git a/play-services-base/src/main/java/com/google/android/gms/common/data/DataBufferRef.java b/play-services-base/src/main/java/com/google/android/gms/common/data/DataBufferRef.java new file mode 100644 index 0000000000000000000000000000000000000000..3c9b8cf1076b4b24276eb530214eed4d14c91b0a --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/common/data/DataBufferRef.java @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.data; + +import android.database.CharArrayBuffer; +import androidx.annotation.NonNull; +import org.microg.gms.common.Hide; + +@Hide +public abstract class DataBufferRef { + protected DataHolder dataHolder; + protected int dataRow; + private int windowIndex; + + public DataBufferRef(DataHolder dataHolder, int dataRow) { + this.dataHolder = dataHolder; + setDataRow(dataRow); + } + + protected void copyToBuffer(@NonNull String column, @NonNull CharArrayBuffer dataOut) { + dataHolder.copyToBuffer(column, dataRow, windowIndex, dataOut); + } + + protected boolean getBoolean(@NonNull String column) { + return dataHolder.getBoolean(column, dataRow, windowIndex); + } + + protected byte[] getByteArray(@NonNull String column) { + return dataHolder.getByteArray(column, dataRow, windowIndex); + } + + protected double getDouble(@NonNull String column) { + return dataHolder.getDouble(column, dataRow, windowIndex); + } + + protected float getFloat(@NonNull String column) { + return dataHolder.getFloat(column, dataRow, windowIndex); + } + + protected int getInteger(@NonNull String column) { + return dataHolder.getInteger(column, dataRow, windowIndex); + } + + protected long getLong(@NonNull String column) { + return dataHolder.getLong(column, dataRow, windowIndex); + } + + protected String getString(@NonNull String column) { + return dataHolder.getString(column, dataRow, windowIndex); + } + + protected boolean hasColumn(@NonNull String column) { + return dataHolder.hasColumn(column); + } + + protected boolean hasNull(@NonNull String column) { + return dataHolder.hasNull(column, dataRow, windowIndex); + } + + public boolean isDataValid() { + return !this.dataHolder.isClosed(); + } + + public void setDataRow(int dataRow) { + this.dataRow = dataRow; + this.windowIndex = dataHolder.getWindowIndex(dataRow); + } +} diff --git a/play-services-base/src/main/java/com/google/android/gms/common/data/DataHolder.java b/play-services-base/src/main/java/com/google/android/gms/common/data/DataHolder.java index 79902d34c19bedd21483bb32110720ed93b70467..a03dfde746fe39c11214f2baab4c1c425ff754c9 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/data/DataHolder.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/data/DataHolder.java @@ -63,6 +63,8 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { private boolean closed = false; private Map columnIndices; + private int[] windowStartPositions; + private int count; protected static final int FIELD_TYPE_NULL = 0; protected static final int FIELD_TYPE_INTEGER = 1; @@ -335,13 +337,15 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { * @return the number of rows in the data holder. */ public int getCount() { - int c = 0; - if (windows != null) { - for (CursorWindow window : windows) { - c += window.getNumRows(); - } - } - return c; + return count; + } + + public double getDouble(String column, int row, int windowIndex) { + return windows[windowIndex].getDouble(row, columnIndices.get(column)); + } + + public float getFloat(String column, int row, int windowIndex) { + return windows[windowIndex].getFloat(row, columnIndices.get(column)); } /** @@ -390,6 +394,10 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { return windows[windowIndex].getString(row, columnIndices.get(column)); } + public boolean hasColumn(String column) { + return columnIndices.values().contains(column); + } + /** * Returns whether the given column at the provided position contains null. * This will throw an {@link IllegalArgumentException} if the column does not exist, the @@ -400,7 +408,7 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { * @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) { + public boolean hasNull(String column, int row, int windowIndex) { return windows[windowIndex].isNull(row, columnIndices.get(column)); } @@ -441,6 +449,21 @@ public class DataHolder extends AutoSafeParcelable implements Closeable { for (int i = 0; i < columns.length; i++) { columnIndices.put(columns[i], i); } + windowStartPositions = new int[windows.length]; + this.count = 0; + for (int windowIndex = 0; windowIndex < windows.length; windowIndex++) { + this.windowStartPositions[windowIndex] = this.count; + this.count += this.windows[windowIndex].getNumRows() - (this.count - windows[windowIndex].getStartPosition()); + } + } + + public int getWindowIndex(int row) { + if (row < 0 || row >= count) throw new IllegalArgumentException(); + int windowIndex = 0; + for (; windowIndex < windowStartPositions.length; windowIndex++) { + if (row < windowStartPositions[windowIndex]) break; + } + return windowIndex-1; } /** diff --git a/play-services-base/src/main/java/com/google/android/gms/common/data/SingleRefDataBufferIterator.java b/play-services-base/src/main/java/com/google/android/gms/common/data/SingleRefDataBufferIterator.java new file mode 100644 index 0000000000000000000000000000000000000000..53da432a9ba1218294569e2c99e9e9d181751787 --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/common/data/SingleRefDataBufferIterator.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.data; + +import androidx.annotation.NonNull; +import org.microg.gms.common.Hide; + +import java.util.NoSuchElementException; + +@Hide +public class SingleRefDataBufferIterator extends DataBufferIterator { + private T element; + + public SingleRefDataBufferIterator(@NonNull DataBuffer dataBuffer) { + super(dataBuffer); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("Cannot advance the iterator beyond " + position); + } + ++position; + if (position == 0) { + element = dataBuffer.get(position); + if (!(element instanceof DataBufferRef)) { + throw new IllegalStateException("DataBuffer reference of type " + element.getClass() + " is not movable"); + } + } else { + ((DataBufferRef) element).setDataRow(position); + } + return element; + } +} diff --git a/play-services-base/src/main/java/com/google/android/gms/common/images/ImageManager.java b/play-services-base/src/main/java/com/google/android/gms/common/images/ImageManager.java new file mode 100644 index 0000000000000000000000000000000000000000..e347fa0e8be46bd1ccc21eefbee5d3f4c40cf110 --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/common/images/ImageManager.java @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2023 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.images; + +import android.content.Context; + +/** + * This class is used to load images from the network and handles local caching for you. + */ +public class ImageManager { + /** + * Returns a new ImageManager for loading images from the network. + * + * @param context The context used by the ImageManager. + * @return A new ImageManager. + */ + public static ImageManager create(Context context) { + throw new UnsupportedOperationException(); + } +} diff --git a/play-services-base/src/main/java/com/google/android/gms/common/images/Size.java b/play-services-base/src/main/java/com/google/android/gms/common/images/Size.java index d17f29f788f679e14e9e79b588feb1d319092edb..4bbf9ff916e31d90a14f988c21cf44c0ae26b711 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/images/Size.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/images/Size.java @@ -1,6 +1,9 @@ /* - * SPDX-FileCopyrightText: 2020, microG Project Team + * 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.images; diff --git a/play-services-base/src/main/java/com/google/android/gms/common/images/WebImage.java b/play-services-base/src/main/java/com/google/android/gms/common/images/WebImage.java index c8f8f2a26a6900708bca4ccf9991f5af538a0189..8b29e2df4c178fdd4474152a6d353a1065158499 100644 --- a/play-services-base/src/main/java/com/google/android/gms/common/images/WebImage.java +++ b/play-services-base/src/main/java/com/google/android/gms/common/images/WebImage.java @@ -1,17 +1,9 @@ /* - * Copyright (C) 2013-2019 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileCopyrightText: 2015 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.images; @@ -20,45 +12,86 @@ import java.util.Locale; import android.net.Uri; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; +/** + * A class that represents an image that is located on a web server. + */ public class WebImage extends AutoSafeParcelable { public static final Creator CREATOR = new AutoCreator(WebImage.class); - public WebImage () { - this.uri = null; - } - - public WebImage (Uri uri) { - this.uri = uri; - } - - @SafeParceled(1) + @Field(1) private int versionCode = 1; - @SafeParceled(2) - private final Uri uri; + @Field(2) + private final Uri url; - @SafeParceled(3) - private final int width = 0; + @Field(3) + private final int width; - @SafeParceled(4) - private final int height = 0; + @Field(4) + private final int height; - public Uri getUrl() { - return uri; + @Hide + private WebImage() { + this.url = null; + this.width = 0; + this.height = 0; } - public int getWidth() { - return width; + /** + * Constructs a new {@link WebImage} with the given URL. + * + * @param url The URL of the image. + * @throws IllegalArgumentException If the URL is null or empty. + */ + public WebImage(Uri url) { + this(url, 0, 0); } + /** + * Constructs a new {@link WebImage} with the given URL and dimensions. + * + * @param url The URL of the image. + * @param width The width of the image, in pixels. + * @param height The height of the image, in pixels. + * @throws IllegalArgumentException If the URL is null or empty, or the dimensions are invalid. + */ + public WebImage(Uri url, int width, int height) { + if (url == null) throw new IllegalArgumentException("url cannot be null"); + if (width < 0 || height < 0) throw new IllegalArgumentException("width and height must not be negative"); + this.url = url; + this.width = width; + this.height = height; + } + + /** + * Gets the image height, in pixels. + */ public int getHeight() { return height; } + /** + * Gets the image URL. + */ + public Uri getUrl() { + return url; + } + + /** + * Gets the image width, in pixels. + */ + public int getWidth() { + return width; + } + + /** + * Returns a string representation of this object. + */ public String toString() { - return String.format(Locale.getDefault(), "Image %dx%d %s", new Object[]{Integer.valueOf(width), Integer.valueOf(height), uri.toString()}); + return String.format(Locale.getDefault(), "Image %dx%d %s", Integer.valueOf(width), Integer.valueOf(height), url.toString()); } } diff --git a/play-services-base/src/main/java/com/google/android/gms/signin/SignIn.java b/play-services-base/src/main/java/com/google/android/gms/signin/SignIn.java new file mode 100644 index 0000000000000000000000000000000000000000..8aca936ca55494b8615598ba725109071539e759 --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/signin/SignIn.java @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.signin; + +import com.google.android.gms.common.api.Api; +import org.microg.gms.common.Hide; +import org.microg.gms.signin.SignInClientImpl; + +@Hide +public class SignIn { + public static final Api API = new Api<>((options, context, looper, clientSettings, callbacks, connectionFailedListener) -> new SignInClientImpl(context, clientSettings, callbacks, connectionFailedListener)); +} diff --git a/play-services-base/src/main/java/com/google/android/gms/signin/SignInClient.java b/play-services-base/src/main/java/com/google/android/gms/signin/SignInClient.java new file mode 100644 index 0000000000000000000000000000000000000000..53f85c89b9bb6e3e943ac8ad7bbdf1d5058fb198 --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/signin/SignInClient.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.signin; + +import androidx.annotation.NonNull; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.internal.IAccountAccessor; +import com.google.android.gms.signin.internal.ISignInCallbacks; + +public interface SignInClient extends Api.Client { + void clearAccountFromSessionStore(); + void saveDefaultAccount(@NonNull IAccountAccessor accountAccessor, boolean crossClient); + void signIn(@NonNull ISignInCallbacks callbacks); +} diff --git a/play-services-base/src/main/java/com/google/android/gms/signin/SignInOptions.java b/play-services-base/src/main/java/com/google/android/gms/signin/SignInOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..be607901aefbeb79561f0970fb442ae6acfcd15c --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/signin/SignInOptions.java @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.signin; + +import com.google.android.gms.common.api.Api; + +public class SignInOptions implements Api.ApiOptions.Optional { +} diff --git a/play-services-base/src/main/java/com/google/android/gms/signin/internal/AuthAccountResult.java b/play-services-base/src/main/java/com/google/android/gms/signin/internal/AuthAccountResult.java new file mode 100644 index 0000000000000000000000000000000000000000..e880bb3f10f75ee299c29300c1c7f27c8300e2d3 --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/signin/internal/AuthAccountResult.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.signin.internal; + +import android.content.Intent; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.Result; +import com.google.android.gms.common.api.Status; +import org.microg.gms.common.Hide; +import org.microg.safeparcel.AutoSafeParcelable; + +@Hide +public class AuthAccountResult extends AutoSafeParcelable implements Result { + @Field(1) + private int versionCode = 2; + @Field(2) + public int connectionResultCode; + @Field(3) + public Intent rawAuthResolutionIntent; + + @Override + public Status getStatus() { + return connectionResultCode == ConnectionResult.SUCCESS ? Status.SUCCESS : Status.CANCELED; + } + + public static final Creator CREATOR = findCreator(AuthAccountResult.class); +} diff --git a/play-services-base/src/main/java/com/google/android/gms/signin/internal/CheckServerAuthResult.java b/play-services-base/src/main/java/com/google/android/gms/signin/internal/CheckServerAuthResult.java index 39b4b762de8704bc2467d5fca8963a95b0ab0c1a..543e2de7e66a93e725795f35e6b845b2519eaf79 100644 --- a/play-services-base/src/main/java/com/google/android/gms/signin/internal/CheckServerAuthResult.java +++ b/play-services-base/src/main/java/com/google/android/gms/signin/internal/CheckServerAuthResult.java @@ -5,8 +5,20 @@ package com.google.android.gms.signin.internal; +import com.google.android.gms.common.api.Scope; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; +import java.util.List; + +@Hide public class CheckServerAuthResult extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(CheckServerAuthResult.class); + @Field(1) + private int versionCode = 1; + @Field(2) + public boolean newAuthCodeRequired; + @Field(3) + public List additionalScopes; + + public static final Creator CREATOR = findCreator(CheckServerAuthResult.class); } diff --git a/play-services-base/src/main/java/com/google/android/gms/signin/internal/RecordConsentByConsentResultResponse.java b/play-services-base/src/main/java/com/google/android/gms/signin/internal/RecordConsentByConsentResultResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..269c5d681035ab672e741a1162c0db2f2ba4ad1f --- /dev/null +++ b/play-services-base/src/main/java/com/google/android/gms/signin/internal/RecordConsentByConsentResultResponse.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.signin.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class RecordConsentByConsentResultResponse extends AutoSafeParcelable { + public static final Creator CREATOR = new AutoCreator<>(RecordConsentByConsentResultResponse.class); +} diff --git a/play-services-base/src/main/java/com/google/android/gms/signin/internal/RecordConsentRequest.java b/play-services-base/src/main/java/com/google/android/gms/signin/internal/RecordConsentRequest.java index 39d650927b4ba3b1619a5d4beac75a314907ee34..ebc8e55f29d195731aa80cb549af13f950f6dcde 100644 --- a/play-services-base/src/main/java/com/google/android/gms/signin/internal/RecordConsentRequest.java +++ b/play-services-base/src/main/java/com/google/android/gms/signin/internal/RecordConsentRequest.java @@ -5,8 +5,21 @@ package com.google.android.gms.signin.internal; +import android.accounts.Account; +import com.google.android.gms.common.api.Scope; +import org.microg.gms.common.Hide; import org.microg.safeparcel.AutoSafeParcelable; +@Hide public class RecordConsentRequest extends AutoSafeParcelable { - public static final Creator CREATOR = new AutoCreator<>(RecordConsentRequest.class); + @Field(1) + private int versionCode = 1; + @Field(2) + public Account account; + @Field(3) + public Scope[] scopesToConsent; + @Field(4) + public String serverClientId; + + public static final Creator CREATOR = findCreator(RecordConsentRequest.class); } diff --git a/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java b/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java index 132a6148fd93effcf66c60b63961d10d52e4571c..c527866f83a4d33e8eeb2ba72159655b4aa66454 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java +++ b/play-services-base/src/main/java/org/microg/gms/common/DummyApiClient.java @@ -16,9 +16,9 @@ package org.microg.gms.common; -import org.microg.gms.common.api.ApiClient; +import com.google.android.gms.common.api.Api; -public class DummyApiClient implements ApiClient { +public class DummyApiClient implements Api.Client { private boolean connected = false; @Override diff --git a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java index 25ebe6cd330da8ba8e859b60e1fcc609826a5af6..e5b717175cff38bac57783ca1b206a07b52027d2 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java +++ b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java @@ -27,17 +27,17 @@ 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.ApiClient; import org.microg.gms.common.api.ConnectionCallbacks; import org.microg.gms.common.api.OnConnectionFailedListener; -public abstract class GmsClient implements ApiClient { +public abstract class GmsClient implements Api.Client { private static final String TAG = "GmsClient"; private final Context context; @@ -69,8 +69,7 @@ public abstract class GmsClient implements ApiClient { throw new IllegalStateException("Service ID not set in constructor and onConnectedToBroker not implemented"); } GetServiceRequest request = new GetServiceRequest(serviceId); - request.extras = new Bundle(); - request.packageName = context.getPackageName(); + request.packageName = packageName; request.account = account; request.extras = extras; broker.getService(callbacks, request); 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 index 8013d70f040d84adb7e6535bc6427852a2326c07..b77c6d2c05c89bc34e204c811070b85b2c8e170d 100644 --- 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 @@ -27,10 +27,9 @@ 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.ApiClient; import org.microg.gms.common.api.GoogleApiClientImpl; -public class GmsConnector { +public class GmsConnector { private static final String TAG = "GmsConnector"; private final GoogleApiClientImpl apiClient; @@ -43,7 +42,7 @@ public class GmsConnector { this.callback = callback; } - public static PendingResult call(GoogleApiClient client, Api api, GmsConnector.Callback callback) { + public static PendingResult call(GoogleApiClient client, Api api, GmsConnector.Callback callback) { return new GmsConnector(client, api, callback).connect(); } 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 b5aa386fb37f18c101f4b8bdb1e8d7c58d367674..18336fa54356f78e6a61f024be8b402f22e8755e 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 d1e314a748713b01eef05d68fde06357ba654ba9..af67e79984042dd5876dfade5808ea1ad4eb1796 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 112336970ae9437320da8f78fcb0550a09750c86..2dffa374f2bb00666af0475f863811d971345850 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,7 +19,6 @@ package org.microg.gms.common.api; import android.content.Context; import android.os.Bundle; import android.os.Looper; -import android.os.Message; import androidx.fragment.app.FragmentActivity; @@ -44,7 +43,7 @@ public class GoogleApiClientImpl implements GoogleApiClient { private final Looper looper; private final ApiClientSettings clientSettings; private final Map apis = new HashMap(); - private final Map apiConnections = new HashMap(); + private final Map apiConnections = new HashMap(); private final Set connectionCallbacks = new HashSet(); private final Set connectionFailedListeners = new HashSet(); private final int clientId; @@ -90,6 +89,10 @@ public class GoogleApiClientImpl implements GoogleApiClient { this.connectionFailedListeners.addAll(connectionFailedListeners); this.clientId = clientId; + if (this.clientSettings.sessionId == null) { + this.clientSettings.sessionId = hashCode(); + } + for (Api api : apis.keySet()) { apiConnections.put(api, api.getBuilder().build(apis.get(api), context, looper, clientSettings, baseConnectionCallbacks, baseConnectionFailedListener)); } @@ -108,7 +111,7 @@ public class GoogleApiClientImpl implements GoogleApiClient { return looper; } - public ApiClient getApiConnection(Api api) { + public Api.Client getApiConnection(Api api) { return apiConnections.get(api); } @@ -138,7 +141,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(); } @@ -151,7 +154,7 @@ public class GoogleApiClientImpl implements GoogleApiClient { shouldDisconnect = true; } else { Log.d(TAG, "disconnect()"); - for (ApiClient connection : apiConnections.values()) { + for (Api.Client connection : apiConnections.values()) { if (connection.isConnected()) { connection.disconnect(); } @@ -161,7 +164,7 @@ public class GoogleApiClientImpl implements GoogleApiClient { @Override public synchronized boolean isConnected() { - for (ApiClient connection : apiConnections.values()) { + for (Api.Client connection : apiConnections.values()) { if (!connection.isConnected()) return false; } return true; @@ -169,7 +172,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 0f68b7c3204e3548f1cc2d6f27c9d3fb92cc1ed7..5cf7933ed64654ac80605875b26b5852cab06ef4 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 @@ -7,11 +7,11 @@ package org.microg.gms.common.api; import android.content.Context; import android.os.Bundle; -import android.os.DeadObjectException; 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; @@ -22,7 +22,7 @@ import java.util.Map; public class GoogleApiManager { private static GoogleApiManager instance; private Context context; - private Map clientMap = new HashMap<>(); + private Map clientMap = new HashMap<>(); private Map>> waitingApiCallMap = new HashMap<>(); private GoogleApiManager(Context context) { @@ -34,19 +34,19 @@ public class GoogleApiManager { return instance; } - private synchronized A clientForApi(GoogleApi api) { + private synchronized A clientForApi(GoogleApi api) { ApiInstance apiInstance = new ApiInstance(api); if (clientMap.containsKey(apiInstance)) { return (A) clientMap.get(apiInstance); } else { - ApiClient client = api.api.getBuilder().build(api.getOptions(), context, context.getMainLooper(), null, new ConnectionCallback(apiInstance), new ConnectionFailedListener(apiInstance)); + 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) { + public synchronized void scheduleTask(GoogleApi api, PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { A client = clientForApi(api); boolean connecting = client.isConnecting(); boolean connected = client.isConnected(); @@ -57,7 +57,7 @@ public class GoogleApiManager { completionSource.setException(e); } } else { - waitingApiCallMap.get(new ApiInstance(api)).add(new WaitingApiCall((PendingGoogleApiCall) apiCall, completionSource)); + waitingApiCallMap.get(new ApiInstance(api)).add(new WaitingApiCall((PendingGoogleApiCall) apiCall, completionSource)); if (!connecting) { client.connect(); } @@ -120,15 +120,15 @@ public class GoogleApiManager { } private static class WaitingApiCall { - private PendingGoogleApiCall apiCall; + private PendingGoogleApiCall apiCall; private TaskCompletionSource completionSource; - public WaitingApiCall(PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { + public WaitingApiCall(PendingGoogleApiCall apiCall, TaskCompletionSource completionSource) { this.apiCall = apiCall; this.completionSource = completionSource; } - public void execute(ApiClient client) throws Exception { + public void execute(Api.Client client) throws Exception { apiCall.execute(client, completionSource); } 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 index cffea83280646b5da2819605007f1a9b22cf2f4b..578d02b99e70585f547302c15e575ab7cec1dda1 100644 --- 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 @@ -5,8 +5,9 @@ package org.microg.gms.common.api; +import com.google.android.gms.common.api.Api; import com.google.android.gms.tasks.TaskCompletionSource; -public interface PendingGoogleApiCall { +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/ReturningGoogleApiCall.java b/play-services-base/src/main/java/org/microg/gms/common/api/ReturningGoogleApiCall.java index 601f3576fdbfd5024ee9d3dc924fe4f7f4ac928b..f836b9d422e3d6529ce796c45accf26f1456cd47 100644 --- 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 @@ -5,9 +5,10 @@ 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 { +public interface ReturningGoogleApiCall extends PendingGoogleApiCall { R execute(A client) throws Exception; @Override 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 index 79778e1abe8c36b073313d3ae62fd3281be18650..ee8cbf6603c027c49df04e76f09471f95edf2a0f 100644 --- 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 @@ -5,9 +5,10 @@ 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{ +public interface VoidReturningGoogleApiCall extends PendingGoogleApiCall{ void execute(A client) throws Exception; @Override diff --git a/play-services-base/src/main/java/org/microg/gms/signin/SignInClientImpl.java b/play-services-base/src/main/java/org/microg/gms/signin/SignInClientImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..7793d8dc2d150c2bf53e6c2b9bd9b1aa1cdf24ce --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/signin/SignInClientImpl.java @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.signin; + +import android.accounts.Account; +import android.content.Context; +import android.os.IBinder; +import android.util.Log; +import androidx.annotation.NonNull; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.internal.IAccountAccessor; +import com.google.android.gms.common.internal.ResolveAccountRequest; +import com.google.android.gms.signin.SignInClient; +import com.google.android.gms.signin.internal.ISignInCallbacks; +import com.google.android.gms.signin.internal.ISignInService; +import com.google.android.gms.signin.internal.SignInRequest; +import com.google.android.gms.signin.internal.SignInResponse; +import org.microg.gms.auth.AuthConstants; +import org.microg.gms.common.GmsClient; +import org.microg.gms.common.GmsService; +import org.microg.gms.common.api.ApiClientSettings; +import org.microg.gms.common.api.ConnectionCallbacks; +import org.microg.gms.common.api.OnConnectionFailedListener; + +public class SignInClientImpl extends GmsClient implements SignInClient { + private static final String TAG = "SignInClientImpl"; + private final int sessionId; + private final Account account; + + public SignInClientImpl(Context context, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) { + super(context, callbacks, connectionFailedListener, GmsService.SIGN_IN.ACTION); + serviceId = GmsService.SIGN_IN.SERVICE_ID; + + account = new Account(clientSettings.accountName != null ? clientSettings.accountName : AuthConstants.DEFAULT_ACCOUNT, AuthConstants.DEFAULT_ACCOUNT_TYPE); + extras.putParcelable("com.google.android.gms.signin.internal.clientRequestedAccount", account); + + sessionId = clientSettings.sessionId; + extras.putInt("com.google.android.gms.common.internal.ClientSettings.sessionId", sessionId); + + extras.putBoolean("com.google.android.gms.signin.internal.offlineAccessRequested", false); + extras.putBoolean("com.google.android.gms.signin.internal.idTokenRequested", false); + extras.putString("com.google.android.gms.signin.internal.serverClientId", null); + extras.putBoolean("com.google.android.gms.signin.internal.usePromptModeForAuthCode", true); + extras.putBoolean("com.google.android.gms.signin.internal.forceCodeForRefreshToken", false); + extras.putString("com.google.android.gms.signin.internal.hostedDomain", null); + extras.putString("com.google.android.gms.signin.internal.logSessionId", null); + extras.putBoolean("com.google.android.gms.signin.internal.waitForAccessTokenRefresh", false); + + if (clientSettings.packageName != null && !context.getPackageName().equals(clientSettings.packageName)) { + extras.putString("com.google.android.gms.signin.internal.realClientPackageName", clientSettings.packageName); + } + } + + @Override + protected ISignInService interfaceFromBinder(IBinder binder) { + return ISignInService.Stub.asInterface(binder); + } + + @Override + public void clearAccountFromSessionStore() { + try { + getServiceInterface().clearAccountFromSessionStore(sessionId); + } catch (Exception e) { + Log.w(TAG, e); + } + } + + @Override + public void saveDefaultAccount(@NonNull IAccountAccessor accountAccessor, boolean crossClient) { + try { + getServiceInterface().saveDefaultAccount(accountAccessor, sessionId, crossClient); + } catch (Exception e) { + Log.w(TAG, e); + } + } + + @Override + public void signIn(@NonNull ISignInCallbacks callbacks) { + try { + SignInRequest request = new SignInRequest(); + request.request = new ResolveAccountRequest(); + request.request.account = account; + request.request.sessionId = sessionId; + if (account.name.equals(AuthConstants.DEFAULT_ACCOUNT)) { + request.request.signInAccountHint = Storage.getInstance(getContext()).getSavedDefaultGoogleSignInAccount(); + } + getServiceInterface().signIn(request, callbacks); + } catch (Exception e) { + Log.w(TAG, e); + try { + SignInResponse response = new SignInResponse(); + response.connectionResult = new ConnectionResult(ConnectionResult.INTERNAL_ERROR); + callbacks.onSignIn(response); + } catch (Exception ignored) { + } + } + } +} diff --git a/play-services-base/src/main/java/org/microg/gms/signin/Storage.java b/play-services-base/src/main/java/org/microg/gms/signin/Storage.java new file mode 100644 index 0000000000000000000000000000000000000000..a12d23956676bc09a20c542070a56b14125f7cd7 --- /dev/null +++ b/play-services-base/src/main/java/org/microg/gms/signin/Storage.java @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.signin; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import org.microg.gms.common.Hide; + +@Hide +@SuppressLint("StaticFieldLeak") +public class Storage { + private static Object LOCK = new Object(); + private static Storage INSTANCE; + + public static Storage getInstance(Context context) { + synchronized (LOCK) { + if (INSTANCE == null) { + INSTANCE = new Storage(context.getApplicationContext()); + } + } + return INSTANCE; + } + + private static final String PREF_DEFAULT_ACCOUNT = "defaultGoogleSignInAccount"; + private static final String PREF_PREFIX_ACCOUNT = "googleSignInAccount:"; + private static final String PREF_PREFIX_OPTIONS = "googleSignInOptions:"; + private final SharedPreferences sharedPreferences; + + public Storage(Context context) { + this.sharedPreferences = context.getSharedPreferences("com.google.android.gms.signin", Context.MODE_PRIVATE); + } + + @Nullable + public GoogleSignInAccount getSavedDefaultGoogleSignInAccount() { + synchronized (sharedPreferences) { + String defaultGoogleSignInAccountName = sharedPreferences.getString(PREF_DEFAULT_ACCOUNT, null); + if (defaultGoogleSignInAccountName == null) return null; + String googleSignInAccountJson = sharedPreferences.getString(PREF_PREFIX_ACCOUNT + defaultGoogleSignInAccountName, null); + if (googleSignInAccountJson == null) return null; + try { + return GoogleSignInAccount.fromJson(googleSignInAccountJson); + } catch (Exception e) { + return null; + } + } + } + + @Nullable + public GoogleSignInOptions getSavedDefaultGoogleSignInOptions() { + synchronized (sharedPreferences) { + String defaultGoogleSignInAccountName = sharedPreferences.getString(PREF_DEFAULT_ACCOUNT, null); + if (defaultGoogleSignInAccountName == null) return null; + String googleSignInOptionsJson = sharedPreferences.getString(PREF_PREFIX_OPTIONS + defaultGoogleSignInAccountName, null); + if (googleSignInOptionsJson == null) return null; + try { + return GoogleSignInOptions.fromJson(googleSignInOptionsJson); + } catch (Exception e) { + return null; + } + } + } + + public void saveDefaultGoogleSignInAccount(@NonNull GoogleSignInAccount googleSignInAccount, @NonNull GoogleSignInOptions googleSignInOptions) { + synchronized (sharedPreferences) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(PREF_DEFAULT_ACCOUNT, googleSignInAccount.getObfuscatedIdentifier()); + editor.putString(PREF_PREFIX_ACCOUNT + googleSignInAccount.getObfuscatedIdentifier(), googleSignInAccount.toJson()); + editor.putString(PREF_PREFIX_OPTIONS + googleSignInAccount.getObfuscatedIdentifier(), googleSignInOptions.toJson()); + editor.apply(); + } + } +} diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index d986c84d6e68e18965239620cc8b7a962d597f9c..f2105d7ee78425b68ff755a5c448beb83422ac57 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -23,6 +23,8 @@ dependencies { api "androidx.collection:collection:1.0.0" api "androidx.core:core:1.2.0" api "androidx.fragment:fragment:1.0.0" + + annotationProcessor project(':safe-parcel-processor') } android { @@ -32,6 +34,7 @@ android { buildToolsVersion "$androidBuildVersionTools" aidlPackagedList "com/google/android/gms/common/api/Status.aidl" + aidlPackagedList "com/google/android/gms/common/ConnectionResult.aidl" aidlPackagedList "com/google/android/gms/common/internal/IAccountAccessor.aidl" aidlPackagedList "com/google/android/gms/common/internal/ICancelToken.aidl" aidlPackagedList "com/google/android/gms/common/server/FavaDiagnosticsEntity.aidl" @@ -47,6 +50,7 @@ android { minSdkVersion androidMinSdk targetSdkVersion androidTargetSdk buildConfigField "int", "VERSION_CODE", "$appVersionCode" + consumerProguardFile 'consumer-rules.pro' } compileOptions { diff --git a/play-services-basement/consumer-rules.pro b/play-services-basement/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..76ba483623da8387e8b9293f6cdc82d7efb6d67a --- /dev/null +++ b/play-services-basement/consumer-rules.pro @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2023 microG Project Team +# SPDX-License-Identifier: CC0-1.0 + +# Keep AutoSafeParcelables +-keep public class * extends org.microg.safeparcel.AutoSafeParcelable { + @com.google.android.gms.common.internal.safeparcel.SafeParcelable$Field *; + @org.microg.safeparcel.SafeParceled *; +} + +# Keep asInterface method cause it's accessed from SafeParcel +-keepattributes InnerClasses +-keep public class * extends android.os.IInterface { + public static * asInterface(android.os.IBinder); +} +-keep public class * extends android.os.Binder { public static *; } + +# Keep name of SafeParcelables and their creators +-keepnames public class * implements com.google.android.gms.common.internal.safeparcel.SafeParcelable +-keepnames public class * implements com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter \ No newline at end of file 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 0000000000000000000000000000000000000000..e46e6d5d6745006669c107865f6dec15dbd67076 --- /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 0000000000000000000000000000000000000000..fab15aa19c87982addf59b32357a37d892f6a9ff --- /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 0000000000000000000000000000000000000000..e9b4bff32e95982c456228d31d9cc42e355bc8c1 --- /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 0000000000000000000000000000000000000000..e6aedfefdea8677748b02efaf916fecc573afe43 --- /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-basement/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/GoogleCertificatesQuery.aidl deleted file mode 100644 index ae02e068b4d6c8d63344864e6c0275e42d669ab8..0000000000000000000000000000000000000000 --- a/play-services-basement/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-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsServiceBroker.aidl b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsServiceBroker.aidl index 9649150d44362db6302ae0df719bd3f957fbfc8b..7ec24ebc469d1f9bffa59824490b31cf7fb2630a 100644 --- a/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsServiceBroker.aidl +++ b/play-services-basement/src/main/aidl/com/google/android/gms/common/internal/IGmsServiceBroker.aidl @@ -17,20 +17,20 @@ interface IGmsServiceBroker { void getGoogleLocationManagerService(IGmsCallbacks callback, int code, String str, in Bundle params) = 7; void getGamesService(IGmsCallbacks callback, int code, String packageName, String accountName, in String[] scopes, String gamePackageName, IBinder popupWindowToken, String desiredLocale, in Bundle params) = 8; void getAppStateService(IGmsCallbacks callback, int code, String packageName, String accountName, in String[] scopes) = 9; - void getPlayLogService(IGmsCallbacks callback, int code, String str, in Bundle params) = 10; - void getAdMobService(IGmsCallbacks callback, int code, String str, in Bundle params) = 11; - void getDroidGuardService(IGmsCallbacks callback, int code, String str, in Bundle params) = 12; - void getLockboxService(IGmsCallbacks callback, int code, String str, in Bundle params) = 13; - void getCastMirroringService(IGmsCallbacks callback, int code, String str, in Bundle params) = 14; - void getNetworkQualityService(IGmsCallbacks callback, int code, String str, in Bundle params) = 15; - void getGoogleIdentityService(IGmsCallbacks callback, int code, String str, in Bundle params) = 16; - void getGoogleFeedbackService(IGmsCallbacks callback, int code, String str, in Bundle params) = 17; - void getCastService(IGmsCallbacks callback, int code, String str, IBinder binder, in Bundle params) = 18; - void getDriveService(IGmsCallbacks callback, int code, String str1, in String[] args, String str2, in Bundle params) = 19; - void getLightweightAppDataSearchService(IGmsCallbacks callback, int code, String str) = 20; - void getSearchAdministrationService(IGmsCallbacks callback, int code, String str) = 21; - void getAutoBackupService(IGmsCallbacks callback, int code, String str, in Bundle params) = 22; - void getAddressService(IGmsCallbacks callback, int code, String str) = 23; + void getPlayLogService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 10; + void getAdMobService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 11; + void getDroidGuardService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 12; + void getLockboxService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 13; + void getCastMirroringService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 14; + void getNetworkQualityService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 15; + void getGoogleIdentityService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 16; + void getGoogleFeedbackService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 17; + void getCastService(IGmsCallbacks callback, int code, String packageName, IBinder binder, in Bundle params) = 18; + void getDriveService(IGmsCallbacks callback, int code, String packageName, in String[] args, String str2, in Bundle params) = 19; + void getLightweightAppDataSearchService(IGmsCallbacks callback, int code, String packageName) = 20; + void getSearchAdministrationService(IGmsCallbacks callback, int code, String packageName) = 21; + void getAutoBackupService(IGmsCallbacks callback, int code, String packageName, in Bundle params) = 22; + void getAddressService(IGmsCallbacks callback, int code, String packageName) = 23; void getWalletServiceWithPackageName(IGmsCallbacks callback, int code, String packageName) = 41; 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 index c75982bb6ec74934e7a242955dde2ba09ff75365..6d54ad26173cd8521c70b29cbbce89010620fa02 100644 --- 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 @@ -1,12 +1,18 @@ package com.google.android.gms.common.internal; -import com.google.android.gms.common.internal.GoogleCertificatesQuery; +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(); - IObjectWrapper getGoogleReleaseCertificates(); - boolean isGoogleReleaseSigned(String packageName, IObjectWrapper certData); - boolean isGoogleSigned(String packageName, IObjectWrapper certData); - boolean isGoogleOrPlatformSigned(in GoogleCertificatesQuery query, IObjectWrapper packageManager); + 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-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 5177385b5c133d7706c4d625faaead95ee5ee3d1..58d8aaf40c25d1c2fe736bf4c723ddc50e1d8bf3 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 @@ -12,8 +12,12 @@ import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; import android.content.IntentSender; +import android.os.Parcel; import android.text.TextUtils; -import org.microg.safeparcel.AutoSafeParcelable; +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 java.util.Arrays; @@ -21,7 +25,8 @@ import java.util.Arrays; * Contains all possible error codes for when a client fails to connect to Google Play services. * These error codes are used by {@link GoogleApiClient.OnConnectionFailedListener}. */ -public class ConnectionResult extends AutoSafeParcelable { +@SafeParcelable.Class +public class ConnectionResult extends AbstractSafeParcelable { /** * The connection was successful. */ @@ -158,12 +163,12 @@ public class ConnectionResult extends AutoSafeParcelable { public static final int DRIVE_EXTERNAL_STORAGE_REQUIRED = 1500; @Field(1) - private final int versionCode = 1; - @Field(2) + int versionCode = 1; + @Field(value = 2, getterName = "getErrorCode") private int statusCode; - @Field(3) + @Field(value = 3, getterName = "getResolution") private PendingIntent resolution; - @Field(4) + @Field(value = 4, getterName = "getErrorMessage") private String message; private ConnectionResult() { @@ -195,7 +200,8 @@ public class ConnectionResult extends AutoSafeParcelable { * @param resolution A pending intent that will resolve the issue when started, or null. * @param message An additional error message for the connection result, or null. */ - public ConnectionResult(int statusCode, PendingIntent resolution, String message) { + @Constructor + public ConnectionResult(@Param(2) int statusCode, @Param(3) PendingIntent resolution, @Param(4) String message) { this.statusCode = statusCode; this.resolution = resolution; this.message = message; @@ -342,5 +348,10 @@ public class ConnectionResult extends AutoSafeParcelable { } } - public static final Creator CREATOR = new AutoCreator<>(ConnectionResult.class); + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ConnectionResult.class); } diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/Feature.java b/play-services-basement/src/main/java/com/google/android/gms/common/Feature.java index 0a5b0813d0650629cdb31e01778c764cb64a05b9..f95bd7fd88dc90f9bd4f5b20578c990ac0d31bb9 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/Feature.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/Feature.java @@ -5,20 +5,26 @@ package com.google.android.gms.common; -import org.microg.safeparcel.AutoSafeParcelable; - -public class Feature extends AutoSafeParcelable { - @Field(1) +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; + +@SafeParcelable.Class +public class Feature extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getName") private String name; @Field(2) - private int oldVersion; - @Field(3) + int oldVersion; + @Field(value = 3, getterName = "getVersion", defaultValue = "-1") private long version = -1; private Feature() { } - public Feature(String name, long version) { + @Constructor + public Feature(@Param(1) String name, @Param(3) long version) { this.name = name; this.version = version; } @@ -32,5 +38,10 @@ public class Feature extends AutoSafeParcelable { return version; } - public static final Creator CREATOR = new AutoSafeParcelable.AutoCreator<>(Feature.class); + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(Feature.class); } 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 0000000000000000000000000000000000000000..ab8da97a6144210ccb4dd35549785f1613a43017 --- /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 0000000000000000000000000000000000000000..90e0ee3a4020854d72853419c1f331b2c3f78881 --- /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-basement/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 58% rename from play-services-basement/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 0e863145fd8fc0b48cbbaaf18f0a624338afdec9..dce40a905fbfba503748d57b8bdd0bba92095424 100644 --- a/play-services-basement/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,43 +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 { - @Field(1) - private String packageName; +@Hide +@SafeParcelable.Class +public class GoogleCertificatesQuery extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getCallingPackage") + String callingPackage; @Field(2) - private IBinder certDataBinder; + IBinder certDataBinder; private CertData certData; @Field(3) - private boolean allowNonRelease; + boolean allowTestKeys; @Field(4) - private boolean allowTestKeys; + boolean ignoreTestKeysOverride; - public String getPackageName() { - return packageName; + public String getCallingPackage() { + return callingPackage; } public CertData getCertData() { @@ -73,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 index 76a4cd291725c11e55da451986eb432ee2a611c1..5c86fb816f7cbac126fda616406bc28fd5fb1738 100644 --- 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 @@ -60,6 +60,8 @@ public class Scopes { public static final String GAMES = "https://www.googleapis.com/auth/games"; @Hide public static final String GAMES_LITE = "https://www.googleapis.com/auth/games_lite"; + @Hide + public static final String GAMES_FIRSTPARTY = "https://www.googleapis.com/auth/games.firstparty"; /** * Scope for using the CloudSave service. */ @@ -88,4 +90,8 @@ public class Scopes { public static final String FITNESS_BODY_READ = "https://www.googleapis.com/auth/fitness.body.read"; @Hide public static final String FITNESS_BODY_READ_WRITE = "https://www.googleapis.com/auth/fitness.body.write"; + @Hide + public static final String USERINFO_EMAIL = "https://www.googleapis.com/auth/userinfo.email"; + @Hide + public static final String USERINFO_PROFILE = "https://www.googleapis.com/auth/userinfo.profile"; } 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 d99932b223c4a5664a7d8e5c015cf03d08996012..37165e36ab404af3e5309689ac08789a9451099a 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,19 +16,23 @@ package com.google.android.gms.common.api; +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 org.microg.gms.common.PublicApi; -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; /** * Describes an OAuth 2.0 scope to request. This has security implications for the user, and * requesting additional scopes will result in authorization dialogs. */ @PublicApi -public class Scope extends AutoSafeParcelable { - @SafeParceled(1) - private int versionCode = 1; - @SafeParceled(2) +@SafeParcelable.Class +public class Scope extends AbstractSafeParcelable { + @Field(1) + int versionCode = 1; + @Field(value = 2, getterName = "getScopeUri") private final String scopeUri; private Scope() { @@ -38,7 +42,8 @@ public class Scope extends AutoSafeParcelable { /** * Creates a new scope with the given URI. */ - public Scope(String scopeUri) { + @Constructor + public Scope(@Param(2) String scopeUri) { this.scopeUri = scopeUri; } @@ -61,5 +66,10 @@ public class Scope extends AutoSafeParcelable { return scopeUri; } - public static final Creator CREATOR = new AutoCreator(Scope.class); + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(Scope.class); } 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 d5a06814f5d1525e7c845dcf358dc2851241d017..af1c543ca29f71bac923f98babae7139c36f5c6c 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 @@ -21,16 +21,20 @@ import android.app.PendingIntent; import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; - +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 org.microg.gms.common.PublicApi; -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; +import org.microg.gms.utils.ToStringHelper; /** * Represents the results of work. */ @PublicApi -public final class Status extends AutoSafeParcelable implements Result { +@SafeParcelable.Class +public final class Status extends AbstractSafeParcelable implements Result { @PublicApi(exclude = true) public static final Status INTERNAL_ERROR = new Status(CommonStatusCodes.INTERNAL_ERROR, "Internal error"); @PublicApi(exclude = true) @@ -38,16 +42,16 @@ public final class Status extends AutoSafeParcelable implements Result { @PublicApi(exclude = true) public static final Status SUCCESS = new Status(CommonStatusCodes.SUCCESS, "Success"); - @SafeParceled(1000) - private int versionCode = 1; + @Field(1000) + int versionCode = 1; - @SafeParceled(1) + @Field(value = 1, getterName = "getStatusCode") private final int statusCode; - @SafeParceled(2) + @Field(value = 2, getterName = "getStatusMessage") private final String statusMessage; - @SafeParceled(3) + @Field(value = 3, getterName = "getResolution") private final PendingIntent resolution; private Status() { @@ -82,7 +86,8 @@ public final class Status extends AutoSafeParcelable implements Result { * @param statusMessage The message associated with this status, or null. * @param resolution A pending intent that will resolve the issue when started, or null. */ - public Status(int statusCode, String statusMessage, PendingIntent resolution) { + @Constructor + public Status(@Param(1) int statusCode, @Param(2) String statusMessage, @Param(3) PendingIntent resolution) { this.statusCode = statusCode; this.statusMessage = statusMessage; this.resolution = resolution; @@ -176,5 +181,16 @@ public final class Status extends AutoSafeParcelable implements Result { } } - public static final Creator CREATOR = new AutoCreator(Status.class); + @NonNull + @Override + public String toString() { + return ToStringHelper.name("Status").field("code", statusCode).field("message", statusMessage).field("resolution", resolution).end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(Status.class); } 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 index 5afe6d10333759ef92f746e563ce9477ddd5f39a..19326d08fbbf276a8e6ed27b22c898aa96150edd 100644 --- 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 @@ -6,12 +6,15 @@ 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; -import org.microg.safeparcel.AutoSafeParcelable; - -public class ConnectionInfo extends AutoSafeParcelable { +@SafeParcelable.Class +public class ConnectionInfo extends AbstractSafeParcelable { @Field(1) public Bundle params; @Field(2) @@ -19,5 +22,10 @@ public class ConnectionInfo extends AutoSafeParcelable { @Field(3) public int unknown3; - public static final Creator CREATOR = new AutoSafeParcelable.AutoCreator<>(ConnectionInfo.class); + @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/GetServiceRequest.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java index 0b1cc52f91242563b73fa0dfedb42cbf0b63d47d..6ff0d849726e02ec773a0a7901b7190a0b7421c0 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 @@ -19,20 +19,22 @@ package com.google.android.gms.common.internal; import android.accounts.Account; import android.os.Bundle; import android.os.IBinder; - +import android.os.Parcel; +import androidx.annotation.NonNull; import com.google.android.gms.common.Feature; import com.google.android.gms.common.api.Scope; - +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 org.microg.gms.common.Constants; import org.microg.gms.common.GmsService; -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; import java.util.Arrays; -public class GetServiceRequest extends AutoSafeParcelable { +@SafeParcelable.Class +public class GetServiceRequest extends AbstractSafeParcelable { @Field(1) - private int versionCode = 6; + int versionCode = 6; @Field(2) public final int serviceId; @Field(3) @@ -49,29 +51,30 @@ public class GetServiceRequest extends AutoSafeParcelable { public Account account; @Field(9) @Deprecated - private long field9; + long field9; @Field(10) public Feature[] defaultFeatures; @Field(11) public Feature[] apiFeatures; @Field(12) - private boolean field12; + boolean supportsConnectionInfo; @Field(13) - private int field13; + int field13; @Field(14) - private boolean field14; + boolean field14; @Field(15) - private String attributionTag; + String attributionTag; private GetServiceRequest() { serviceId = -1; gmsVersion = Constants.GMS_VERSION_CODE; } - public GetServiceRequest(int serviceId) { + @Constructor + public GetServiceRequest(@Param(2) int serviceId) { this.serviceId = serviceId; this.gmsVersion = Constants.GMS_VERSION_CODE; - this.field12 = true; + this.supportsConnectionInfo = true; } @Override @@ -86,5 +89,10 @@ public class GetServiceRequest extends AutoSafeParcelable { '}'; } - public static Creator CREATOR = new AutoCreator(GetServiceRequest.class); + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = findCreator(GetServiceRequest.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 71337291671bac987e1673f88f960bd237f8498a..1cf0ecc19f7c5a99755c4f1b8f861f90fc35b3de 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/SafeParcelable.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java index 9583d017513a873802a3d82b7ae16faa11c4571b..0d8d1cc0448446d3423086cb459f9f43b1d7e554 100644 --- 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 @@ -43,5 +43,13 @@ public interface SafeParcelable extends Parcelable { 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/dynamite/DynamiteModule.java b/play-services-basement/src/main/java/com/google/android/gms/dynamite/DynamiteModule.java index 70d45e546394af190c37252f0704d52598a2d1ae..2d708c988bbed0393e486869dd16215a7ff1857f 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/dynamite/DynamiteModule.java +++ b/play-services-basement/src/main/java/com/google/android/gms/dynamite/DynamiteModule.java @@ -52,10 +52,8 @@ public class DynamiteModule { public interface VersionPolicy { interface IVersions { - /* renamed from: zza */ int getLocalVersion(@NonNull Context context, @NonNull String moduleId); - /* renamed from: zzb */ int getRemoteVersion(@NonNull Context context, @NonNull String moduleId, boolean forceStaging) throws LoadingException; IVersions Default = new IVersions() { 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 3f6541b3f42d6764160ec2f524c7711c7c233e46..e57822a9e9bbf7f527c5fa3ecc5d00e299d85b1d 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 @@ -165,8 +165,10 @@ public enum GmsService { 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") + IN_APP_REACH(315, "com.google.android.gms.inappreach.service.START"), + APP_ERRORS(334, "com.google.android.gms.apperrors.service.START_APP_ERROR"), ; public int SERVICE_ID; diff --git a/play-services-basement/src/main/java/org/microg/safeparcel/AutoSafeParcelable.java b/play-services-basement/src/main/java/org/microg/safeparcel/AutoSafeParcelable.java index 09e0ccbf0ad234b753c0fd1b81e7cb2b48cded7f..cc66bc64c37a5251a02cfcf61844feb1e027a272 100644 --- a/play-services-basement/src/main/java/org/microg/safeparcel/AutoSafeParcelable.java +++ b/play-services-basement/src/main/java/org/microg/safeparcel/AutoSafeParcelable.java @@ -16,13 +16,13 @@ public abstract class AutoSafeParcelable extends AbstractSafeParcelable { @SuppressWarnings("unchecked") @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(Parcel dest, int flags) { Creator creator = SafeParcelReflectionUtil.getCreator(this.getClass()); if (creator instanceof SafeParcelableCreatorAndWriter) { - ((SafeParcelableCreatorAndWriter) (SafeParcelableCreatorAndWriter) creator).writeToParcel(this, out, flags); + ((SafeParcelableCreatorAndWriter) (SafeParcelableCreatorAndWriter) creator).writeToParcel(this, dest, flags); } else { Log.w(TAG, "AutoSafeParcelable is not using SafeParcelableCreatorAndWriter"); - SafeParcelReflectionUtil.writeObject(this, out, flags); + SafeParcelReflectionUtil.writeObject(this, dest, flags); } } diff --git a/play-services-cast/src/main/java/org/microg/gms/cast/CastApiClientBuilder.java b/play-services-cast/src/main/java/org/microg/gms/cast/CastApiClientBuilder.java index aba489712051bea9a4f94f437c1d58c100f78d3f..a7d9247f9cae3a6cc348ae935cfd0736d24fdf14 100644 --- a/play-services-cast/src/main/java/org/microg/gms/cast/CastApiClientBuilder.java +++ b/play-services-cast/src/main/java/org/microg/gms/cast/CastApiClientBuilder.java @@ -21,15 +21,15 @@ import android.os.Looper; import com.google.android.gms.cast.Cast; +import com.google.android.gms.common.api.Api; import org.microg.gms.common.api.ApiClientBuilder; import org.microg.gms.common.api.ApiClientSettings; -import org.microg.gms.common.api.ApiClient; import org.microg.gms.common.api.ConnectionCallbacks; import org.microg.gms.common.api.OnConnectionFailedListener; public class CastApiClientBuilder implements ApiClientBuilder { @Override - public ApiClient build(Cast.CastOptions options, Context context, Looper looper, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) { + public Api.Client build(Cast.CastOptions options, Context context, Looper looper, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) { return new CastClientImpl(context, options, callbacks, connectionFailedListener); } } diff --git a/play-services-cast/src/main/java/org/microg/gms/cast/CastRemoteDisplayApiClientBuilder.java b/play-services-cast/src/main/java/org/microg/gms/cast/CastRemoteDisplayApiClientBuilder.java index 588d25c3eba8ddbf1df9829ae9e0d0795c94832d..db53af366ae1f1704b12e210a60dc020565f1772 100644 --- a/play-services-cast/src/main/java/org/microg/gms/cast/CastRemoteDisplayApiClientBuilder.java +++ b/play-services-cast/src/main/java/org/microg/gms/cast/CastRemoteDisplayApiClientBuilder.java @@ -21,16 +21,16 @@ import android.os.Looper; import com.google.android.gms.cast.CastRemoteDisplay; +import com.google.android.gms.common.api.Api; import org.microg.gms.common.DummyApiClient; import org.microg.gms.common.api.ApiClientBuilder; import org.microg.gms.common.api.ApiClientSettings; -import org.microg.gms.common.api.ApiClient; import org.microg.gms.common.api.ConnectionCallbacks; import org.microg.gms.common.api.OnConnectionFailedListener; public class CastRemoteDisplayApiClientBuilder implements ApiClientBuilder { @Override - public ApiClient build(CastRemoteDisplay.CastRemoteDisplayOptions options, Context context, Looper looper, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) { + public Api.Client build(CastRemoteDisplay.CastRemoteDisplayOptions options, Context context, Looper looper, ApiClientSettings clientSettings, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) { return new DummyApiClient(); } } diff --git a/play-services-clearcut/build.gradle b/play-services-clearcut/build.gradle index c28d1e0fbf54116602e27ebd9756c1aded43a328..b8f604ce9f19daffb6435523db254aa2c9fcec85 100644 --- a/play-services-clearcut/build.gradle +++ b/play-services-clearcut/build.gradle @@ -35,12 +35,10 @@ apply from: '../gradle/publish-android.gradle' description = 'microG implementation of play-services-clearcut' dependencies { - implementation project(':play-services-api') - // Dependencies from play-services-clearcut:17.0.0 api "androidx.core:core:1.0.0" api project(':play-services-base') api project(':play-services-basement') -// api project(':play-services-phenotype') + api project(':play-services-phenotype') api project(':play-services-tasks') } diff --git a/play-services-clearcut/src/main/java/com/google/android/gms/clearcut/LogEventParcelable.java b/play-services-clearcut/src/main/java/com/google/android/gms/clearcut/LogEventParcelable.java index 152d3b1d975cdab8f4088528f3a44422e22f8711..64aee6dde537c8986ee68013380215b2e2933d06 100644 --- a/play-services-clearcut/src/main/java/com/google/android/gms/clearcut/LogEventParcelable.java +++ b/play-services-clearcut/src/main/java/com/google/android/gms/clearcut/LogEventParcelable.java @@ -19,7 +19,7 @@ package com.google.android.gms.clearcut; import android.util.Base64; import com.google.android.gms.clearcut.internal.LogVerifierResultParcelable; -import com.google.android.gms.phenotype.ExperimentToken; +import com.google.android.gms.phenotype.ExperimentTokens; import com.google.android.gms.phenotype.GenericDimension; import com.google.android.gms.clearcut.internal.PlayLoggerContext; @@ -57,7 +57,7 @@ public class LogEventParcelable extends AutoSafeParcelable { public final boolean addPhenotypeExperimentTokens; @Field(9) - public final ExperimentToken[] experimentTokenParcelables; + public final ExperimentTokens[] experimentTokenParcelables; @Field(10) public final GenericDimension[] genericDimensions; diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 1254f45538e691c739580a49675312cb1042a76e..2bd8475f422e2e445b49ae4dcd540fb41a10ae8a 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -6,17 +6,21 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +def hasModule(String name, boolean enabledByDefault) { + return localProperties.getProperty("modules." + name, enabledByDefault.toString()).toBoolean() +} + configurations { - withMapboxImplementation - withVtmImplementation - withNearbyImplementation - withoutNearbyImplementation + mapboxRuntimeOnly + vtmRuntimeOnly + defaultRuntimeOnly } dependencies { implementation "com.squareup.wire:wire-runtime:$wireVersion" implementation "de.hdodenhof:circleimageview:1.3.0" + implementation project(':fake-signature') implementation project(':firebase-dynamic-links') implementation project(':firebase-auth-core') implementation project(':play-services-ads-core') @@ -34,8 +38,6 @@ dependencies { implementation project(':play-services-gmscompliance-core') implementation project(':play-services-location-core') implementation project(':play-services-location-core-base') - implementation project(':play-services-location-core-provider') - withNearbyImplementation project(':play-services-nearby-core') implementation project(':play-services-oss-licenses-core') implementation project(':play-services-pay-core') implementation project(':play-services-recaptcha-core') @@ -54,6 +56,7 @@ dependencies { implementation project(':play-services-auth') implementation project(':play-services-clearcut') implementation project(':play-services-drive') + implementation project(':play-services-games') implementation project(':play-services-maps') implementation project(':play-services-measurement-base') implementation project(':play-services-places') @@ -61,8 +64,11 @@ dependencies { implementation project(':play-services-safetynet') implementation project(':play-services-tasks-ktx') - withMapboxImplementation project(':play-services-maps-core-mapbox') - withVtmImplementation project(':play-services-maps-core-vtm') + mapboxRuntimeOnly project(':play-services-maps-core-mapbox') + vtmRuntimeOnly project(':play-services-maps-core-vtm') + defaultRuntimeOnly project(':play-services-location-core-provider') + + if (hasModule("nearby", true)) runtimeOnly project(':play-services-nearby-core-package') // AndroidX UI implementation "androidx.multidex:multidex:$multidexVersion" @@ -122,12 +128,6 @@ android { main { java.srcDirs += 'src/main/kotlin' } - withNearby { - java.srcDirs += 'src/withNearby/kotlin' - } - withoutNearby { - java.srcDirs += 'src/withoutNearby/kotlin' - } } lintOptions { @@ -187,21 +187,16 @@ android { } } - flavorDimensions 'maps', 'nearby' + flavorDimensions = ['maps', 'target'] productFlavors { - withMapbox { - dimension 'maps' + "default" { + dimension 'target' } - withVtm { + "mapbox" { dimension 'maps' - versionNameSuffix '-vtm' } - withNearby { - dimension 'nearby' - } - withoutNearby { - dimension 'nearby' - versionNameSuffix '-noen' + "vtm" { + dimension 'maps' } } diff --git a/play-services-core/multidex-keep.pro b/play-services-core/multidex-keep.pro index 6bf5055e3c23b749818b9cbba5b83f6e9ab1b177..66c36b8cbd26e583a279ec2f926cb2046374a256 100644 --- a/play-services-core/multidex-keep.pro +++ b/play-services-core/multidex-keep.pro @@ -1,5 +1,12 @@ -# Make sure maps is in primary dex file +# 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 Dynamite Loader in the primary dex file otherwise it will error out on legacy Android versions +-keep class com.google.android.gms.chimera.container.DynamiteLoaderImpl { *; } + +# 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/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index a2e082737068a9296462b7229940d241da28a8ca..67aa30cfed66967ecccceaeb23a2f92e5886a3ee 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -69,7 +69,9 @@ android:protectionLevel="normal" /> - + - - @@ -137,10 +137,6 @@ - - - + + + + + + - + - + + + + + + + + + + + + + + @@ -719,18 +739,6 @@ - - - - - - - - - - - - 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 0000000000000000000000000000000000000000..3605e8ac04f6f8609924a59cb1c195a8c4047d8a --- /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 8eefeeeaf6abd943a6a1538bb2c0b955daab2508..0000000000000000000000000000000000000000 --- a/play-services-core/src/main/java/com/google/android/gms/common/GoogleCertificatesImpl.java +++ /dev/null @@ -1,76 +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 androidx.annotation.Keep; -import android.util.Log; - -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 036bc8e5819e88d5b92497f7db7cad7018f96d4d..c358c6c962cb2ea652d323cfc6a8f0cfa37c54c6 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 @@ -59,7 +59,6 @@ public class AccountContentProvider extends ContentProvider { suggestedPackageName = getCallingPackage(); } String packageName = PackageUtils.getAndCheckCallingPackage(getContext(), suggestedPackageName); - Log.d(TAG, "Call " + method + " from " + packageName + " with arg " + arg); if (!PackageUtils.callerHasExtendedAccess(getContext())) { String[] packagesForUid = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid()); if (packagesForUid != null && packagesForUid.length != 0) @@ -93,7 +92,6 @@ public class AccountContentProvider extends ContentProvider { } result.putParcelableArray(PROVIDER_EXTRA_ACCOUNTS, accounts); - Log.d(TAG, "get_accounts returns: " + Arrays.toString(accounts)); return result; } else if (PROVIDER_METHOD_CLEAR_PASSWORD.equals(method) && PackageUtils.callerHasExtendedAccess(getContext())) { Account a = extras.getParcelable(PROVIDER_EXTRA_CLEAR_PASSWORD); 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 8ba6c6a939aa1acfda180c18262e4372b7754128..57d61aba9629663d1ea8f3e1f3f565f369f00443 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 @@ -1,27 +1,19 @@ /* - * 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. + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.auth; import android.accounts.Account; import android.accounts.AccountManager; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; +import android.net.Uri; import android.util.Log; +import androidx.annotation.RequiresPermission; import org.microg.gms.common.PackageUtils; import org.microg.gms.settings.SettingsContract; @@ -48,6 +40,16 @@ public class AuthManager { private String packageSignature; private String accountType; + + private int delegationType; + private String delegateeUserId; + private String oauth2Foreground; + private String oauth2Prompt; + private String itCaveatTypes; + private String tokenRequestOptions; + public String includeEmail; + public String includeProfile; + public AuthManager(Context context, String accountName, String packageName, String service) { this.context = context; this.accountName = accountName; @@ -80,7 +82,15 @@ public class AuthManager { } public String buildTokenKey(String service) { - return packageName + ":" + getPackageSignature() + ":" + service; + Uri.Builder builder = Uri.EMPTY.buildUpon(); + if (delegationType != 0 && delegateeUserId != null) + builder.appendQueryParameter("delegation_type", Integer.toString(delegationType)) + .appendQueryParameter("delegatee_user_id", delegateeUserId); + if (tokenRequestOptions != null) builder.appendQueryParameter("token_request_options", tokenRequestOptions); + if (includeEmail != null) builder.appendQueryParameter("include_email", includeEmail); + if (includeProfile != null) builder.appendQueryParameter("include_profile", includeEmail); + String query = builder.build().getEncodedQuery(); + return packageName + ":" + getPackageSignature() + ":" + service + (query != null ? ("?" + query) : ""); } public String buildTokenKey() { @@ -124,6 +134,32 @@ public class AuthManager { getAccountManager().setUserData(getAccount(), key, value); } + public void setDelegation(int delegationType, String delegateeUserId) { + if (delegationType != 0 && delegateeUserId != null) { + this.delegationType = delegationType; + this.delegateeUserId = delegateeUserId; + } else { + this.delegationType = 0; + this.delegateeUserId = null; + } + } + + public void setOauth2Foreground(String oauth2Foreground) { + this.oauth2Foreground = oauth2Foreground; + } + + public void setOauth2Prompt(String oauth2Prompt) { + this.oauth2Prompt = oauth2Prompt; + } + + public void setItCaveatTypes(String itCaveatTypes) { + this.itCaveatTypes = itCaveatTypes; + } + + public void setTokenRequestOptions(String tokenRequestOptions) { + this.tokenRequestOptions = tokenRequestOptions; + } + public boolean accountExists() { for (Account refAccount : getAccountManager().getAccountsByType(accountType)) { if (refAccount.name.equalsIgnoreCase(accountName)) return true; @@ -138,7 +174,7 @@ public class AuthManager { public String getAuthToken() { if (service.startsWith("weblogin:")) return null; - if (getExpiry() < System.currentTimeMillis() / 1000L) { + if (System.currentTimeMillis() / 1000L >= getExpiry() - 300L) { Log.d(TAG, "token present, but expired"); return null; } @@ -167,6 +203,16 @@ public class AuthManager { } } + public void invalidateAuthToken() { + String authToken = peekAuthToken(); + invalidateAuthToken(authToken); + } + + @SuppressLint("MissingPermission") + public void invalidateAuthToken(String auth) { + getAccountManager().invalidateAuthToken(accountType, auth); + } + public void storeResponse(AuthResponse response) { if (service.startsWith("weblogin:")) return; if (response.accountId != null) @@ -206,6 +252,10 @@ public class AuthManager { AuthResponse response = new AuthResponse(); response.issueAdvice = "stored"; response.auth = token; + if (service.startsWith("oauth2:")) { + response.grantedScopes = service.substring(7); + } + response.expiry = getExpiry(); return response; } } @@ -214,9 +264,16 @@ public class AuthManager { .app(packageName, getPackageSignature()) .email(accountName) .token(getAccountManager().getPassword(account)) - .service(service); - if (isSystemApp()) request.systemPartition(); - if (isPermitted()) request.hasPermission(); + .service(service) + .delegation(delegationType, delegateeUserId) + .oauth2Foreground(oauth2Foreground) + .oauth2Prompt(oauth2Prompt) + .oauth2IncludeProfile(includeProfile) + .oauth2IncludeEmail(includeEmail) + .itCaveatTypes(itCaveatTypes) + .tokenRequestOptions(tokenRequestOptions) + .systemPartition(isSystemApp()) + .hasPermission(isPermitted()); if (legacy) { request.callerIsGms().calledFromAccountManager(); } else { 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 2df0a462802cb31eb31dcddc8b2280142a262285..896149467f0bdb6d33cdf1b3d70f8ff3a27deb5e 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 @@ -18,8 +18,6 @@ package org.microg.gms.auth; import android.accounts.Account; import android.accounts.AccountManager; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; import android.annotation.SuppressLint; import android.app.NotificationManager; import android.app.PendingIntent; @@ -40,7 +38,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; @@ -70,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"; public static final String KEY_USER_RECOVERY_INTENT = "userRecoveryIntent"; @@ -116,7 +116,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/), @@ -125,6 +126,10 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", ""); AuthManager authManager = new AuthManager(context, account.name, packageName, scope); + if (extras.containsKey(KEY_DELEGATION_TYPE) && extras.getInt(KEY_DELEGATION_TYPE) != 0 ) { + authManager.setDelegation(extras.getInt(KEY_DELEGATION_TYPE), extras.getString("delegatee_user_id")); + } + authManager.setOauth2Foreground(notify ? "0" : "1"); Bundle result = new Bundle(); result.putString(KEY_ACCOUNT_NAME, account.name); result.putString(KEY_ACCOUNT_TYPE, authManager.getAccountType()); @@ -135,10 +140,11 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { try { AuthResponse res = authManager.requestAuth(false); if (res.auth != null) { - Log.d(TAG, "getToken: " + res); + if (!AuthConstants.SCOPE_GET_ACCOUNT_ID.equals(scope)) + Log.d(TAG, "getToken: " + res); result.putString(KEY_AUTHTOKEN, res.auth); Bundle details = new Bundle(); - details.putParcelable("TokenData", new TokenData(res.auth, res.expiry, scope.startsWith("oauth2:"), getScopes(scope))); + details.putParcelable("TokenData", new TokenData(res.auth, res.expiry, scope.startsWith("oauth2:"), getScopes(res.grantedScopes != null ? res.grantedScopes : scope))); result.putBundle("tokenDetails", details); result.putString(KEY_ERROR, "OK"); } else { @@ -217,7 +223,7 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override - public int hasCapabilities(HasCababilitiesRequest request) throws RemoteException { + public int hasCapabilities(HasCapabilitiesRequest request) throws RemoteException { Log.w(TAG, "Not implemented: hasCapabilities(" + request.account + ", " + Arrays.toString(request.capabilities) + ")"); return 1; } 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 d0bb11443aa55bd1c50373bc7ce77531a167ca72..294f5e8a2feb3f28101a7ad202230ac8dfe67aea 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 @@ -1,17 +1,6 @@ /* - * 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. + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.auth; @@ -24,6 +13,7 @@ import org.microg.gms.common.Constants; import org.microg.gms.common.HttpFormClient; import org.microg.gms.common.Utils; import org.microg.gms.profile.ProfileManager; +import org.microg.gms.settings.SettingsContract; import java.io.IOException; import java.util.Locale; @@ -84,6 +74,26 @@ public class AuthRequest extends HttpFormClient.Request { public boolean hasPermission; @RequestContent("add_account") public boolean addAccount; + @RequestContent("delegation_type") + public String delegationType; + @RequestContent("delegatee_user_id") + public String delegateeUserId; + @RequestContent("oauth2_foreground") + public String oauth2Foreground; + @RequestContent("token_request_options") + public String tokenRequestOptions; + @RequestContent("it_caveat_types") + public String itCaveatTypes; + @RequestContent("check_email") + public boolean checkEmail; + @RequestContent("request_visible_actions") + public String requestVisibleActions; + @RequestContent("oauth2_prompt") + public String oauth2Prompt; + @RequestContent("oauth2_include_profile") + public String oauth2IncludeProfile; + @RequestContent("oauth2_include_email") + public String oauth2IncludeEmail; public String deviceName; public String buildVersion; @@ -168,13 +178,13 @@ public class AuthRequest extends HttpFormClient.Request { return this; } - public AuthRequest systemPartition() { - systemPartition = true; + public AuthRequest systemPartition(boolean systemPartition) { + this.systemPartition = systemPartition; return this; } - public AuthRequest hasPermission() { - hasPermission = true; + public AuthRequest hasPermission(boolean hasPermission) { + this.hasPermission = hasPermission; return this; } @@ -193,6 +203,42 @@ public class AuthRequest extends HttpFormClient.Request { return this; } + public AuthRequest delegation(int delegationType, String delegateeUserId) { + this.delegationType = delegationType == 0 ? null : Integer.toString(delegationType); + this.delegateeUserId = delegateeUserId; + return this; + } + + public AuthRequest oauth2Foreground(String oauth2Foreground) { + this.oauth2Foreground = oauth2Foreground; + return this; + } + + public AuthRequest tokenRequestOptions(String tokenRequestOptions) { + this.tokenRequestOptions = tokenRequestOptions; + return this; + } + + public AuthRequest oauth2IncludeProfile(String oauth2IncludeProfile) { + this.oauth2IncludeProfile = oauth2IncludeProfile; + return this; + } + + public AuthRequest oauth2IncludeEmail(String oauth2IncludeProfile) { + this.oauth2IncludeEmail = oauth2IncludeEmail; + return this; + } + + public AuthRequest oauth2Prompt(String oauth2Prompt) { + this.oauth2Prompt = oauth2Prompt; + return this; + } + + public AuthRequest itCaveatTypes(String itCaveatTypes) { + this.itCaveatTypes = itCaveatTypes; + return this; + } + public AuthResponse getResponse() throws IOException { return HttpFormClient.request(SERVICE_URL, this, AuthResponse.class); } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthResponse.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthResponse.java index f94eacef5ec8f92174d048f48fcab34be9a3a1ff..3380991031a0585f973ecccac6312f19383157c8 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthResponse.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthResponse.java @@ -63,6 +63,14 @@ public class AuthResponse { public String scopeConsentDetails; @ResponseField("ConsentDataBase64") public String consentDataBase64; + @ResponseField("grantedScopes") + public String grantedScopes; + @ResponseField("itMetadata") + public String itMetadata; + @ResponseField("ResolutionDataBase64") + public String resolutionDataBase64; + @ResponseField("it") + public String auths; public static AuthResponse parse(String result) { AuthResponse response = new AuthResponse(); @@ -115,6 +123,7 @@ public class AuthResponse { if (permission != null) sb.append(", permission='").append(permission).append('\''); if (scopeConsentDetails != null) sb.append(", scopeConsentDetails='").append(scopeConsentDetails).append('\''); if (consentDataBase64 != null) sb.append(", consentDataBase64='").append(consentDataBase64).append('\''); + if (auths != null) sb.append(", auths='").append(auths).append('\''); sb.append('}'); return sb.toString(); } 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 76c273abe1f68f71ca82d0982c1040bb9da46e15..0793d362bd3801789bd2e538e71c3e2709eab3b6 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 @@ -21,7 +21,6 @@ import android.accounts.AccountManager; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; -import android.content.Intent; import android.graphics.Color; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -42,7 +41,6 @@ import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.StringRes; -import androidx.core.app.OnNewIntentProvider; import androidx.webkit.WebViewClientCompat; import com.google.android.gms.R; @@ -56,14 +54,12 @@ import org.microg.gms.checkin.CheckinManager; import org.microg.gms.checkin.LastCheckinInfo; import org.microg.gms.common.HttpFormClient; import org.microg.gms.common.Utils; -import org.microg.gms.droidguard.core.DroidGuardResultCreator; import org.microg.gms.people.PeopleManager; import org.microg.gms.profile.Build; import org.microg.gms.profile.ProfileManager; import java.io.IOException; import java.security.MessageDigest; -import java.util.Collections; import java.util.Locale; import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE; @@ -342,8 +338,8 @@ public class LoginActivity extends AssistantActivity { .service(authManager.getService()) .email(account.name) .token(AccountManager.get(this).getPassword(account)) - .systemPartition() - .hasPermission() + .systemPartition(true) + .hasPermission(true) .addAccount() .getAccountId() .getResponseAsync(new HttpFormClient.Callback() { 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 5b4118480c3cced3bf4b419f8860d46b7d187a8a..7891ddfbd1cb52c801a3d39d8478d6c6ab750e1d 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 @@ -140,7 +140,6 @@ class AccountAuthenticator extends AbstractAccountAuthenticator { @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { - Log.d(TAG, "hasFeatures: " + account + ", " + Arrays.toString(features)); AccountManager accountManager = AccountManager.get(context); String services = accountManager.getUserData(account, "services"); boolean res = true; 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 d5290d0c9b4b50c3615be1604bd79ad3ba32c55e..2374d25d6e781a0fdb0d084410740d0a70605598 100644 --- 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 @@ -140,7 +140,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) 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 22211e160bf63f1dc18c52c3913c61e0462cc5b3..1604ed25654f44225ba8243dc65e0c0d5d6f5de8 100644 --- 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 @@ -48,7 +48,7 @@ public class CheckinManager { for (Account account : accountManager.getAccountsByType(accountType)) { String token = new AuthRequest() .email(account.name).token(accountManager.getPassword(account)) - .hasPermission().service("ac2dm") + .hasPermission(true).service("ac2dm") .app("com.google.android.gsf", Constants.GMS_PACKAGE_SIGNATURE_SHA1) .getResponse().LSid; if (token != null) { 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 bcc2f736950423cc780106af2a87d3cab7768d42..149428115ae0b8a6513223958517dac19e2883f3 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)); } } diff --git a/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java b/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java index b0cb1e049f3a1962466ed40e4e6c9fa9988716f5..dd90304b77f0914c2996f55ab3037bc15c811141 100644 --- a/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java @@ -77,7 +77,6 @@ public class GServicesProvider extends ContentProvider { for (String name : cache.keySet()) { if (name.startsWith(prefix)) { String value = cache.get(name); - Log.d(TAG, "query caller=" + getCallingPackageName() + " prefix=" + prefix + " name=" + name + " value=" + value); cursor.addRow(new String[]{name, value}); } } @@ -91,7 +90,6 @@ public class GServicesProvider extends ContentProvider { value = databaseHelper.get(name); cache.put(name, value); } - Log.d(TAG, "query caller=" + getCallingPackageName() + " name=" + name + " value=" + value); if (value != null) { cursor.addRow(new String[]{name, value}); } diff --git a/play-services-core/src/main/java/org/microg/gms/people/DatabaseHelper.java b/play-services-core/src/main/java/org/microg/gms/people/DatabaseHelper.java index 29527e38d8290a8513bf14d5988f5e2c2e56d786..4989414384f200bf38588e3229574b5e86cf329f 100644 --- a/play-services-core/src/main/java/org/microg/gms/people/DatabaseHelper.java +++ b/play-services-core/src/main/java/org/microg/gms/people/DatabaseHelper.java @@ -113,6 +113,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { public void putOwner(ContentValues contentValues) { getWritableDatabase().insertWithOnConflict(OWNERS_TABLE, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE); + close(); } public Cursor getOwner(String accountName) { diff --git a/play-services-core/src/main/java/org/microg/gms/people/PeopleManager.java b/play-services-core/src/main/java/org/microg/gms/people/PeopleManager.java index ccc2442a2dfe0d905361e5fa301a0158c841815b..79331073b5d3e550584a260049c9ab72077dd4df 100644 --- a/play-services-core/src/main/java/org/microg/gms/people/PeopleManager.java +++ b/play-services-core/src/main/java/org/microg/gms/people/PeopleManager.java @@ -25,6 +25,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; +import com.google.android.gms.common.Scopes; import org.json.JSONException; import org.json.JSONObject; import org.microg.gms.auth.AuthManager; @@ -41,7 +42,7 @@ import java.net.URLConnection; public class PeopleManager { private static final String TAG = "GmsPeopleManager"; - public static final String USERINFO_SCOPE = "oauth2:https://www.googleapis.com/auth/userinfo.profile"; + public static final String USERINFO_SCOPE = "oauth2:" + Scopes.USERINFO_PROFILE; public static final String USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo"; public static final String REGEX_SEARCH_USER_PHOTO = "https?\\:\\/\\/lh([0-9]*)\\.googleusercontent\\.com/"; 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 2ea293f44e5f534bd79ed2414fe50286081addb5..1fade6da7120cc9d0f1515d6c6d474af448f9cd8 100644 --- 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 @@ -11,6 +11,9 @@ import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; import com.google.android.gms.R; +import org.microg.gms.ui.settings.SettingsProvider; + +import static org.microg.gms.ui.settings.SettingsProviderKt.getAllSettingsProviders; public class SettingsActivity extends AppCompatActivity { private AppBarConfiguration appBarConfiguration; @@ -24,10 +27,16 @@ public class SettingsActivity extends AppCompatActivity { super.onCreate(savedInstanceState); Intent intent = getIntent(); - NearbyPreferencesIntegration.Companion.preProcessSettingsIntent(intent); + 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); } 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 0000000000000000000000000000000000000000..e1fb7a210833e0e16b813590cf549684c52cbab1 --- /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/account/data/GoogleAuthService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/account/data/GoogleAuthService.kt new file mode 100644 index 0000000000000000000000000000000000000000..1bc2f2d84313a8b6b46dfcfd672e6f33c9eb3a63 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/account/data/GoogleAuthService.kt @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.auth.account.data + +import android.accounts.Account +import android.os.Bundle +import android.os.Parcel +import android.util.Log +import com.google.android.gms.auth.* +import com.google.android.gms.auth.account.data.* +import com.google.android.gms.auth.firstparty.dataservice.ClearTokenRequest +import com.google.android.gms.common.Feature +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.api.internal.IStatusCallback +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 org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "GoogleAuthService" + +val FEATURES = arrayOf( + Feature("auth_suw", 224516000), + Feature("account_capability_api", 1), + Feature("account_data_service", 6), + Feature("account_data_service_legacy", 1), + Feature("account_data_service_token", 8), + Feature("account_data_service_visibility", 1), + Feature("config_sync", 1), + Feature("device_account_api", 1), + Feature("device_account_jwt_creation", 1), + Feature("gaiaid_primary_email_api", 1), + Feature("google_auth_service_accounts", 2), + Feature("google_auth_service_token", 3), + Feature("hub_mode_api", 1), + Feature("user_service_account_management", 1), + Feature("work_account_client_is_whitelisted", 1), +) + +class GoogleAuthService : BaseService(TAG, GmsService.GOOGLE_AUTH){ + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + val binder = GoogleAuthServiceImpl().asBinder() + callback.onPostInitCompleteWithConnectionInfo(CommonStatusCodes.SUCCESS, binder, ConnectionInfo().apply { features = FEATURES }) + } +} + +class GoogleAuthServiceImpl : IGoogleAuthService.Stub() { + override fun getTokenWithDetails(callback: IGetTokenWithDetailsCallback?, account: Account?, service: String?, extras: Bundle?) { + Log.d(TAG, "Not yet implemented: getTokenWithDetails($account, $service, $extras)") + callback?.onTokenResults(Status.INTERNAL_ERROR, Bundle.EMPTY) + } + + override fun clearToken(callback: IStatusCallback?, request: ClearTokenRequest?) { + Log.d(TAG, "Not yet implemented: clearToken($request)") + callback?.onResult(Status.INTERNAL_ERROR) + } + + override fun requestAccountsAccess(callback: IBundleCallback?, str: String?) { + Log.d(TAG, "Not yet implemented: requestAccountsAccess($str)") + callback?.onBundle(Status.INTERNAL_ERROR, Bundle.EMPTY) + } + + override fun getAccountChangeEvents(callback: IGetAccountChangeEventsCallback?, request: AccountChangeEventsRequest?) { + Log.d(TAG, "Not yet implemented: getAccountChangeEvents($request)") + callback?.onAccountChangeEventsResponse(Status.INTERNAL_ERROR, AccountChangeEventsResponse()) + } + + override fun getAccounts(callback: IGetAccountsCallback?, request: GetAccountsRequest?) { + Log.d(TAG, "Not yet implemented: getAccounts($request)") + callback?.onBundle(Status.INTERNAL_ERROR, emptyList()) + } + + override fun removeAccount(callback: IBundleCallback?, account: Account?) { + Log.d(TAG, "Not yet implemented: removeAccount($account)") + callback?.onBundle(Status.INTERNAL_ERROR, Bundle.EMPTY) + } + + override fun hasCapabilities(callback: IHasCapabilitiesCallback?, request: HasCapabilitiesRequest?) { + Log.d(TAG, "Not yet implemented: hasCapabilities($request)") + callback?.onHasCapabilities(Status.INTERNAL_ERROR, 1) + } + + override fun getHubToken(callback: IGetHubTokenCallback?, request: GetHubTokenRequest?) { + Log.d(TAG, "Not yet implemented: getHubToken($request)") + callback?.onGetHubTokenResponse(Status.INTERNAL_ERROR, GetHubTokenInternalResponse()) + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { + super.onTransact(code, data, reply, flags) + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..baaf4eadce97a93e4946b8a40fcf28f01589e725 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInActivity.kt @@ -0,0 +1,215 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.auth.signin + +import android.accounts.Account +import android.accounts.AccountManager +import android.content.Intent +import android.graphics.Bitmap +import android.os.Bundle +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.getSystemService +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.R +import com.google.android.gms.auth.api.signin.GoogleSignInAccount +import com.google.android.gms.auth.api.signin.SignInAccount +import com.google.android.gms.auth.api.signin.internal.SignInConfiguration +import com.google.android.gms.common.Scopes +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.databinding.SigninConfirmBinding +import com.google.android.gms.databinding.SigninPickerBinding +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT +import org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE +import org.microg.gms.auth.login.LoginActivity +import org.microg.gms.people.DatabaseHelper +import org.microg.gms.people.PeopleManager +import org.microg.gms.utils.getApplicationLabel + +private const val TAG = "AuthSignInActivity" +private const val REQUEST_CODE_ADD_ACCOUNT = 100 + +private val ACCEPTABLE_SCOPES = setOf(Scopes.OPENID, Scopes.EMAIL, Scopes.PROFILE, Scopes.USERINFO_EMAIL, Scopes.USERINFO_PROFILE, Scopes.GAMES_LITE) + +/** + * TODO: Get privacy policy / terms of service links via + * https://clientauthconfig.googleapis.com/google.identity.clientauthconfig.v1.ClientAuthConfig/GetDisplayBrand + */ +class AuthSignInActivity : AppCompatActivity() { + private val config: SignInConfiguration? + get() = runCatching { + intent?.extras?.also { it.classLoader = SignInConfiguration::class.java.classLoader }?.getParcelable("config") + }.getOrNull() + + private val Int.px: Int get() = (this * resources.displayMetrics.density).toInt() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setResult(CommonStatusCodes.CANCELED) + + Log.d(TAG, "Request: $config") + + val packageName = config?.packageName + if (packageName == null || (packageName != callingActivity?.packageName && callingActivity?.packageName != packageName)) + return finishResult(CommonStatusCodes.DEVELOPER_ERROR, "package name mismatch") + val accountManager = getSystemService() ?: return finishResult(CommonStatusCodes.INTERNAL_ERROR, "No account manager") + + val unacceptableScopes = config?.options?.scopes.orEmpty().filter { it.scopeUri !in ACCEPTABLE_SCOPES } + if (unacceptableScopes.isNotEmpty()) return finishResult(CommonStatusCodes.DEVELOPER_ERROR, "Unacceptable scopes: $unacceptableScopes") + + val accounts = accountManager.getAccountsByType(DEFAULT_ACCOUNT_TYPE) + if (accounts.isNotEmpty()) { + val account = config?.options?.account + if (account != null) { + if (account in accounts) { + showSignInConfirm(packageName, account) + } else { + finishResult(CommonStatusCodes.INVALID_ACCOUNT) + } + } else { + openAccountPicker(packageName) + } + } else { + openAddAccount() + } + } + + private fun openAddAccount() { + startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_CODE_ADD_ACCOUNT) + } + + private fun getDisplayName(account: Account): String? { + val cursor = DatabaseHelper(this).getOwner(account.name) + return try { + if (cursor.moveToNext()) { + cursor.getColumnIndex("display_name").takeIf { it >= 0 }?.let { cursor.getString(it) }.takeIf { !it.isNullOrBlank() } + } else null + } finally { + cursor.close() + } + } + + private fun bindAccountRow(root: View, account: Account, updateAction: (ImageView, Bitmap) -> Unit) { + val photoView = root.findViewById(R.id.account_photo) + val displayNameView = root.findViewById(R.id.account_display_name) + val emailView = root.findViewById(R.id.account_email) + if (account.name != DEFAULT_ACCOUNT) { + val photo = PeopleManager.getOwnerAvatarBitmap(this@AuthSignInActivity, account.name, false) + if (photo == null) { + lifecycleScope.launchWhenStarted { + val bitmap = withContext(Dispatchers.IO) { + PeopleManager.getOwnerAvatarBitmap(this@AuthSignInActivity, account.name, true) + } + updateAction(photoView, bitmap) + } + } + val displayName = getDisplayName(account) + photoView.setImageBitmap(photo) + if (displayName != null) { + displayNameView.text = displayName + emailView.text = account.name + emailView.visibility = View.VISIBLE + } else { + displayNameView.text = account.name + emailView.visibility = View.GONE + } + } else { + photoView.setImageResource(R.drawable.ic_add_account_alt) + displayNameView.setText(R.string.signin_picker_add_account_label) + emailView.visibility = View.GONE + } + } + + private fun openAccountPicker(packageName: String) { + val binding = SigninPickerBinding.inflate(layoutInflater) + binding.appName = packageManager.getApplicationLabel(packageName).toString() + binding.appIcon = packageManager.getApplicationIcon(packageName) + val accounts = getSystemService()!!.getAccountsByType(DEFAULT_ACCOUNT_TYPE) + Account(DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE) + binding.pickerList.adapter = object : ArrayAdapter(this, 0, accounts) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val v = convertView ?: layoutInflater.inflate(R.layout.signin_account_row, parent, false) + getItem(position)?.let { bindAccountRow(v, it) { _, _ -> notifyDataSetChanged() } } + return v + } + } + binding.pickerList.setOnItemClickListener { parent, view, position, id -> + binding.listProgressSpinner = true + if (accounts[position].name == DEFAULT_ACCOUNT) { + openAddAccount() + } else { + lifecycleScope.launchWhenStarted { + signIn(accounts[position]) + } + } + } + setContentView(binding.root) + } + + private fun showSignInConfirm(packageName: String, account: Account) { + val binding = SigninConfirmBinding.inflate(layoutInflater) + binding.appName = packageManager.getApplicationLabel(packageName).toString() + binding.appIcon = packageManager.getApplicationIcon(packageName) + bindAccountRow(binding.root, account) { view, bitmap -> view.setImageBitmap(bitmap) } + binding.button2.setOnClickListener { + finishResult(CommonStatusCodes.CANCELED) + } + binding.button1.setOnClickListener { + binding.button1.isEnabled = false + binding.button2.isEnabled = false + lifecycleScope.launchWhenStarted { + signIn(account) + } + } + setContentView(binding.root) + } + + private suspend fun signIn(account: Account) { + val googleSignInAccount = performSignIn(this, config?.packageName!!, config?.options, account, true) + if (googleSignInAccount != null) { + finishResult(CommonStatusCodes.SUCCESS, account = account, googleSignInAccount = googleSignInAccount) + } else { + finishResult(CommonStatusCodes.INTERNAL_ERROR, "Sign in failed") + } + } + + private fun finishResult(statusCode: Int, message: String? = null, account: Account? = null, googleSignInAccount: GoogleSignInAccount? = null) { + val data = Intent() + if (statusCode != CommonStatusCodes.SUCCESS) data.putExtra("errorCode", statusCode) + data.putExtra("googleSignInStatus", Status(statusCode, message)) + data.putExtra("googleSignInAccount", googleSignInAccount) + if (googleSignInAccount != null) { + data.putExtra("signInAccount", SignInAccount().apply { + email = googleSignInAccount.email ?: account?.name + this.googleSignInAccount = googleSignInAccount + userId = googleSignInAccount.id ?: getSystemService()?.getUserData(account, "GoogleUserId") + }) + } + Log.d(TAG, "Result: ${data.extras?.also { it.keySet() }}") + setResult(statusCode, data) + finish() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CODE_ADD_ACCOUNT) { + val accountManager = getSystemService() ?: return finish() + val accounts = accountManager.getAccountsByType(DEFAULT_ACCOUNT_TYPE) + if (accounts.isNotEmpty()) { + openAccountPicker(config?.packageName!!) + } else { + finishResult(CommonStatusCodes.CANCELED, "No account and creation cancelled") + } + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInService.kt index a11badabf858106bc0469fa0aa6cf3209db8fd80..1ed8af431011180b95bc837f1f402b618c1a66b7 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInService.kt @@ -15,21 +15,35 @@ */ package org.microg.gms.auth.signin +import android.accounts.Account +import android.content.Context import android.os.Bundle import android.os.Parcel import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.android.volley.toolbox.JsonObjectRequest +import com.android.volley.toolbox.Volley +import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.gms.auth.api.signin.internal.ISignInCallbacks import com.google.android.gms.auth.api.signin.internal.ISignInService +import com.google.android.gms.common.Feature import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.api.Scope import com.google.android.gms.common.api.Status +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 org.microg.gms.BaseService +import org.microg.gms.auth.AuthPrefs import org.microg.gms.common.GmsService import org.microg.gms.common.PackageUtils import org.microg.gms.utils.warnOnTransactionIssues +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine private const val TAG = "AuthSignInService" @@ -37,26 +51,103 @@ class AuthSignInService : BaseService(TAG, GmsService.AUTH_SIGN_IN) { override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) ?: throw IllegalArgumentException("Missing package name") - val binder = SignInServiceImpl(packageName, request.scopes.asList(), request.extras).asBinder() - callback.onPostInitComplete(CommonStatusCodes.SUCCESS, binder, Bundle()) + val binder = AuthSignInServiceImpl(this, lifecycle, packageName, request.account, request.scopes.asList(), request.extras).asBinder() + callback.onPostInitCompleteWithConnectionInfo(CommonStatusCodes.SUCCESS, binder, ConnectionInfo().apply { + features = arrayOf(Feature("user_service_account_management", 1)) + }) } } -class SignInServiceImpl(private val packageName: String, private val scopes: List, private val extras: Bundle) : ISignInService.Stub() { - override fun silentSignIn(callbacks: ISignInCallbacks?, options: GoogleSignInOptions?) { - Log.d(TAG, "Not yet implemented: signIn: $options") - callbacks?.onSignIn(null, Status.INTERNAL_ERROR) +class AuthSignInServiceImpl( + private val context: Context, + private val lifecycle: Lifecycle, + private val packageName: String, + private val account: Account?, + private val scopes: List, + private val extras: Bundle +) : ISignInService.Stub(), LifecycleOwner { + private val queue = Volley.newRequestQueue(context) + override fun getLifecycle(): Lifecycle = lifecycle + + override fun silentSignIn(callbacks: ISignInCallbacks, options: GoogleSignInOptions?) { + Log.d(TAG, "$packageName:silentSignIn($options)") + fun sendResult(account: GoogleSignInAccount?, status: Status) { + Log.d(TAG, "Result[$status]: $account") + runCatching { callbacks.onSignIn(account, status) } + } + lifecycleScope.launchWhenStarted { + try { + val account = account ?: options?.account ?: SignInConfigurationService.getDefaultAccount(context, packageName) + if (account != null && options?.isForceCodeForRefreshToken != true) { + if (getOAuthManager(context, packageName, options, account).isPermitted || AuthPrefs.isTrustGooglePermitted(context)) { + val googleSignInAccount = performSignIn(context, packageName, options, account) + if (googleSignInAccount != null) { + sendResult(googleSignInAccount, Status(CommonStatusCodes.SUCCESS)) + } else { + sendResult(null, Status(CommonStatusCodes.DEVELOPER_ERROR)) + } + } else { + sendResult(null, Status(CommonStatusCodes.SIGN_IN_REQUIRED)) + } + } else { + sendResult(null, Status(CommonStatusCodes.SIGN_IN_REQUIRED)) + } + } catch (e: Exception) { + Log.w(TAG, e) + sendResult(null, Status.INTERNAL_ERROR) + } + } } - override fun signOut(callbacks: ISignInCallbacks?, options: GoogleSignInOptions?) { - Log.d(TAG, "Not yet implemented: signOut: $options") - callbacks?.onSignOut(Status.INTERNAL_ERROR) + override fun signOut(callbacks: ISignInCallbacks, options: GoogleSignInOptions?) { + Log.d(TAG, "$packageName:signOut($options)") + lifecycleScope.launchWhenStarted { + try { + SignInConfigurationService.setDefaultAccount(context, packageName, null) + runCatching { callbacks.onSignOut(Status.SUCCESS) } + } catch (e: Exception) { + Log.w(TAG, e) + runCatching { callbacks.onSignIn(null, Status.INTERNAL_ERROR) } + } + } } - override fun revokeAccess(callbacks: ISignInCallbacks?, options: GoogleSignInOptions?) { - Log.d(TAG, "Not yet implemented: revokeAccess: $options") - callbacks?.onRevokeAccess(Status.INTERNAL_ERROR) + override fun revokeAccess(callbacks: ISignInCallbacks, options: GoogleSignInOptions?) { + Log.d(TAG, "$packageName:revokeAccess($options)") + lifecycleScope.launchWhenStarted { + val account = account ?: options?.account ?: SignInConfigurationService.getDefaultAccount(context, packageName) + if (account != null) { + try { + val authManager = getOAuthManager(context, packageName, options, account) + val token = authManager.peekAuthToken() + if (token != null) { + suspendCoroutine { continuation -> + queue.add(object : JsonObjectRequest( + "https://accounts.google.com/o/oauth2/revoke?token=$token", + { continuation.resume(it) }, + { continuation.resumeWithException(it) }) { + override fun getHeaders(): MutableMap { + return hashMapOf( + "Authorization" to "OAuth $token" + ) + } + }) + } + authManager.invalidateAuthToken(token) + authManager.isPermitted = false + } + SignInConfigurationService.setDefaultAccount(context, packageName, account) + runCatching { callbacks.onRevokeAccess(Status.SUCCESS) } + } catch (e: Exception) { + Log.w(TAG, e) + runCatching { callbacks.onRevokeAccess(Status.INTERNAL_ERROR) } + } + } else { + runCatching { callbacks.onRevokeAccess(Status.SUCCESS) } + } + } } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { 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) } } \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/SignInConfigurationService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/SignInConfigurationService.kt new file mode 100644 index 0000000000000000000000000000000000000000..dec280687bbb4ee80c27733bce12ce8a6ed93a2b --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/SignInConfigurationService.kt @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.auth.signin + +import android.accounts.Account +import android.accounts.AccountManager +import android.app.Service +import android.content.* +import android.os.* +import androidx.core.content.getSystemService +import androidx.core.os.bundleOf +import org.microg.gms.auth.AuthConstants +import org.microg.gms.common.PackageUtils +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +private const val PREFERENCES_NAME = "google_account_cache" +private const val DEFAULT_ACCOUNT_PREFIX = "default_google_account_" + +private const val MSG_GET_DEFAULT_ACCOUNT = 1 +private const val MSG_SET_DEFAULT_ACCOUNT = 2 + +private const val MSG_DATA_PACKAGE_NAME = "package_name" +private const val MSG_DATA_ACCOUNT = "account" + +class SignInConfigurationService : Service() { + private val preferences: SharedPreferences + get() = getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + private val accountManager: AccountManager + get() = getSystemService()!! + + override fun onBind(intent: Intent?): IBinder { + return Messenger(object : Handler(Looper.getMainLooper()) { + override fun handleMessage(msg: Message) { + val data = when (msg.what) { + MSG_GET_DEFAULT_ACCOUNT -> { + val packageName = msg.data?.getString(MSG_DATA_PACKAGE_NAME) + val account = packageName?.let { getDefaultAccount(it) } + bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account + ) + } + + MSG_SET_DEFAULT_ACCOUNT -> { + val packageName = msg.data?.getString(MSG_DATA_PACKAGE_NAME) + val account = msg.data?.getParcelable(MSG_DATA_ACCOUNT) + packageName?.let { setDefaultAccount(it, account) } + bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account + ) + } + + else -> Bundle.EMPTY + } + msg.replyTo?.send(Message.obtain().also { + it.what = msg.what + it.data = data + }) + } + }).binder + } + + private fun getPackageNameSuffix(packageName: String): String { + return packageName + ":" + PackageUtils.firstSignatureDigest(this, packageName) + } + + private fun getDefaultAccount(packageName: String): Account? { + val name = preferences.getString(DEFAULT_ACCOUNT_PREFIX + getPackageNameSuffix(packageName), null) + if (name.isNullOrBlank()) return null + val accounts: Array = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) + for (account in accounts) { + if (account.name.equals(name)) return account + } + return null + } + + private fun setDefaultAccount(packageName: String, account: Account?) { + val editor: SharedPreferences.Editor = preferences.edit() + if (account == null || account.name == AuthConstants.DEFAULT_ACCOUNT) { + editor.remove(DEFAULT_ACCOUNT_PREFIX + getPackageNameSuffix(packageName)) + } else { + editor.putString(DEFAULT_ACCOUNT_PREFIX + getPackageNameSuffix(packageName), account.name) + } + editor.apply() + } + + companion object { + + private suspend fun singleRequest(context: Context, message: Message) = suspendCoroutine { continuation -> + val connection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + val connection = this + message.replyTo = Messenger(object : Handler(Looper.myLooper() ?: Looper.getMainLooper()) { + override fun handleMessage(msg: Message) { + runCatching { continuation.resume(msg) } + runCatching { context.unbindService(connection) } + } + }) + try { + Messenger(service).send(message) + } catch (e: Exception) { + runCatching { continuation.resumeWithException(e) } + runCatching { context.unbindService(connection) } + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + runCatching { continuation.resumeWithException(RuntimeException("Disconnected")) } + } + } + val connected = context.bindService(Intent(context, SignInConfigurationService::class.java), connection, BIND_AUTO_CREATE or BIND_ABOVE_CLIENT) + if (!connected) { + runCatching { continuation.resumeWithException(RuntimeException("Connection failed")) } + runCatching { context.unbindService(connection) } + } + } + + suspend fun getDefaultAccount(context: Context, packageName: String): Account? { + return singleRequest(context, Message.obtain().apply { + what = MSG_GET_DEFAULT_ACCOUNT + data = bundleOf( + MSG_DATA_PACKAGE_NAME to packageName + ) + }).data?.getParcelable(MSG_DATA_ACCOUNT) + } + + suspend fun setDefaultAccount(context: Context, packageName: String, account: Account?) { + singleRequest(context, Message.obtain().apply { + what = MSG_SET_DEFAULT_ACCOUNT + data = bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account + ) + }) + } + + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/extensions.kt new file mode 100644 index 0000000000000000000000000000000000000000..d8658b226db34efb20f5f353d0db8935f3d44f1f --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/extensions.kt @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.auth.signin + +import android.accounts.Account +import android.content.Context +import android.net.Uri +import android.util.Log +import com.google.android.gms.auth.api.signin.GoogleSignInAccount +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import com.google.android.gms.common.Scopes +import com.google.android.gms.common.api.Scope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.auth.AuthManager +import org.microg.gms.people.DatabaseHelper +import org.microg.gms.utils.toHexString +import java.security.MessageDigest +import kotlin.math.min + +private fun Long?.orMaxIfNegative() = this?.takeIf { it >= 0L } ?: Long.MAX_VALUE + +fun getOAuthManager(context: Context, packageName: String, options: GoogleSignInOptions?, account: Account): AuthManager { + val scopes = options?.scopes.orEmpty().sortedBy { it.scopeUri } + return AuthManager(context, account.name, packageName, "oauth2:${scopes.joinToString(" ")}") +} + +suspend fun performSignIn(context: Context, packageName: String, options: GoogleSignInOptions?, account: Account, permitted: Boolean = false): GoogleSignInAccount? { + val authManager = getOAuthManager(context, packageName, options, account) + if (permitted) authManager.isPermitted = true + val authResponse = withContext(Dispatchers.IO) { + authManager.requestAuth(true) + } + if (authResponse.auth == null) return null + + val scopes = options?.scopes.orEmpty().sortedBy { it.scopeUri } + val includeId = scopes.any { it.scopeUri == Scopes.OPENID } || scopes.any { it.scopeUri == Scopes.GAMES_LITE } + val includeEmail = scopes.any { it.scopeUri == Scopes.EMAIL } || scopes.any { it.scopeUri == Scopes.GAMES_LITE } + val includeProfile = scopes.any { it.scopeUri == Scopes.PROFILE } + Log.d("AuthSignIn", "id token requested: ${options?.isIdTokenRequested == true}, serverClientId = ${options?.serverClientId}") + val idTokenResponse = if (options?.isIdTokenRequested == true && options.serverClientId != null) withContext(Dispatchers.IO) { + val idTokenManager = AuthManager(context, account.name, packageName, "audience:server:client_id:${options.serverClientId}") + idTokenManager.includeEmail = if (includeEmail) "1" else "0" + idTokenManager.includeProfile = if (includeProfile) "1" else "0" + idTokenManager.isPermitted = authManager.isPermitted + idTokenManager.requestAuth(true) + } else null + val serverAuthTokenResponse = if (options?.isServerAuthCodeRequested == true && options.serverClientId != null) withContext(Dispatchers.IO) { + val serverAuthTokenManager = AuthManager(context, account.name, packageName, "oauth2:server:client_id:${options.serverClientId}:api_scope:${scopes.joinToString(" ")}") + serverAuthTokenManager.includeEmail = if (includeEmail) "1" else "0" + serverAuthTokenManager.includeProfile = if (includeProfile) "1" else "0" + serverAuthTokenManager.setOauth2Prompt(if (options.isForceCodeForRefreshToken) "consent" else "auto") + serverAuthTokenManager.setItCaveatTypes("2") + serverAuthTokenManager.isPermitted = authManager.isPermitted + serverAuthTokenManager.requestAuth(true) + } else null + val googleUserId = authManager.getUserData("GoogleUserId") + val id = if (includeId) googleUserId else null + val tokenId = if (options?.isIdTokenRequested == true) idTokenResponse?.auth else null + val serverAuthCode: String? = if (options?.isServerAuthCodeRequested == true) serverAuthTokenResponse?.auth else null + val expirationTime = min(authResponse.expiry.orMaxIfNegative(), idTokenResponse?.expiry.orMaxIfNegative()) + val obfuscatedIdentifier: String = MessageDigest.getInstance("MD5").digest("$googleUserId:$packageName".encodeToByteArray()).toHexString().uppercase() + val grantedScopes = authResponse.grantedScopes?.split(" ").orEmpty().map { Scope(it) }.toSet() + val (givenName, familyName, displayName, photoUrl) = if (includeProfile) { + val cursor = DatabaseHelper(context).getOwner(account.name) + try { + if (cursor.moveToNext()) { + listOf( + cursor.getColumnIndex("given_name").takeIf { it >= 0 }?.let { cursor.getString(it) }, + cursor.getColumnIndex("family_name").takeIf { it >= 0 }?.let { cursor.getString(it) }, + cursor.getColumnIndex("display_name").takeIf { it >= 0 }?.let { cursor.getString(it) }, + cursor.getColumnIndex("avatar").takeIf { it >= 0 }?.let { cursor.getString(it) }, + ) + } else listOf(null, null, null, null) + } finally { + cursor.close() + } + } else listOf(null, null, null, null) + SignInConfigurationService.setDefaultAccount(context, packageName, account) + return GoogleSignInAccount( + id, + tokenId, + account.name, + displayName, + photoUrl?.let { Uri.parse(it) }, + serverAuthCode, + expirationTime, + obfuscatedIdentifier, + grantedScopes, + givenName, + familyName + ) +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/FirstPartyGamesService.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/FirstPartyGamesService.kt new file mode 100644 index 0000000000000000000000000000000000000000..c01233fcd9932e7fea9d4af37c559b7c458b01f2 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/games/FirstPartyGamesService.kt @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.games + +import android.content.Context +import android.os.Parcel +import android.util.Log +import androidx.lifecycle.Lifecycle +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.data.DataHolder +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.games.client.IPlayGamesCallbacks +import com.google.android.gms.games.client.IPlayGamesService +import com.google.android.gms.games.client.PlayGamesConsistencyTokens +import org.microg.gms.BaseService +import org.microg.gms.common.Constants +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "PlayGamesService" +private val FIRST_PARTY_PACKAGES = setOf(Constants.GMS_PACKAGE_NAME, GAMES_PACKAGE_NAME) + +class FirstPartyGamesService : BaseService(TAG, GmsService.GAMES) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + val packageName = PackageUtils.getAndCheckCallingPackageOrExtendedAccess(this, request.packageName) + ?: throw IllegalArgumentException("Missing package name") + val callingPackageName = PackageUtils.getCallingPackage(this) ?: packageName + if (!PackageUtils.callerHasExtendedAccess(this)) throw IllegalArgumentException("$callingPackageName does not have extended access") + if (callingPackageName !in FIRST_PARTY_PACKAGES) throw IllegalArgumentException("$callingPackageName is not first-party") + callback.onPostInitCompleteWithConnectionInfo( + CommonStatusCodes.SUCCESS, + PlayGamesServiceImpl(this, lifecycle, packageName), + ConnectionInfo() + ) + } +} + +class PlayGamesServiceImpl(val context: Context, val lifecycle: Lifecycle, val packageName: String) : IPlayGamesService.Stub() { + + override fun getGameCollection(callbacks: IPlayGamesCallbacks?, maxResults: Int, gameCollectionType: Int, z: Boolean, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: getGameCollection($maxResults, $gameCollectionType, $z, $forceReload)") + callbacks?.onData(DataHolder.empty(CommonStatusCodes.SUCCESS)) + } + + override fun loadGames(callbacks: IPlayGamesCallbacks?, playerId: String?, maxResults: Int, z: Boolean, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: loadGames($playerId, $maxResults, $z, $forceReload)") + callbacks?.onData(DataHolder.empty(CommonStatusCodes.SUCCESS)) + } + + override fun getConsistencyTokens(): PlayGamesConsistencyTokens { + Log.d(TAG, "Not yet implemented: getConsistencyTokens") + return PlayGamesConsistencyTokens(null, null) + } + + override fun updateConsistencyTokens(tokens: PlayGamesConsistencyTokens?) { + Log.d(TAG, "Not yet implemented: updateConsistencyTokens($tokens)") + } + + override fun fun5041(callbacks: IPlayGamesCallbacks?) { + callbacks?.onStatus5028(Status.SUCCESS) + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConfigurationService.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConfigurationService.kt new file mode 100644 index 0000000000000000000000000000000000000000..10c0da65561389bed386f75c739c4a0d2d83494f --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConfigurationService.kt @@ -0,0 +1,233 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.games + +import android.accounts.Account +import android.accounts.AccountManager +import android.app.Service +import android.content.* +import android.os.* +import androidx.core.content.getSystemService +import androidx.core.os.bundleOf +import org.microg.gms.auth.AuthConstants +import org.microg.gms.common.PackageUtils +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +private const val PREFERENCES_NAME = "games.config" +private const val PREF_ACCOUNT_GLOBAL = "account_global" +private const val PREF_ACCOUNT_PREFIX = "account_" +private const val PREF_PLAYER_PREFIX = "player_" + +private const val MSG_GET_DEFAULT_ACCOUNT = 1 +private const val MSG_SET_DEFAULT_ACCOUNT = 2 +private const val MSG_GET_PLAYER = 3 +private const val MSG_SET_PLAYER = 4 + +private const val MSG_DATA_PACKAGE_NAME = "package_name" +private const val MSG_DATA_ACCOUNT = "account" +private const val MSG_DATA_PLAYER = "player" + +class GamesConfigurationService : Service() { + private val preferences: SharedPreferences + get() = getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + private val accountManager: AccountManager + get() = getSystemService()!! + + override fun onBind(intent: Intent?): IBinder { + return Messenger(object : Handler(Looper.getMainLooper()) { + override fun handleMessage(msg: Message) { + val data = when (msg.what) { + MSG_GET_DEFAULT_ACCOUNT -> { + val packageName = msg.data?.getString(MSG_DATA_PACKAGE_NAME) + val account = getDefaultAccount(packageName) + bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account + ) + } + + MSG_SET_DEFAULT_ACCOUNT -> { + val packageName = msg.data?.getString(MSG_DATA_PACKAGE_NAME) + val account = msg.data?.getParcelable(MSG_DATA_ACCOUNT) + setDefaultAccount(packageName, account) + bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account + ) + } + + MSG_GET_PLAYER -> { + val packageName = msg.data?.getString(MSG_DATA_PACKAGE_NAME) + val account = msg.data?.getParcelable(MSG_DATA_ACCOUNT) + val player = if (packageName != null && account != null) getPlayer(packageName, account) else null + bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account, + MSG_DATA_PLAYER to player + ) + } + + MSG_SET_PLAYER -> { + val packageName = msg.data?.getString(MSG_DATA_PACKAGE_NAME) + val account = msg.data?.getParcelable(MSG_DATA_ACCOUNT) + val player = msg.data?.getString(MSG_DATA_PLAYER) + if (packageName != null && account != null) setPlayer(packageName, account, player) + bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account, + MSG_DATA_PLAYER to player + ) + } + + else -> Bundle.EMPTY + } + msg.replyTo?.send(Message.obtain().also { + it.what = msg.what + it.data = data + }) + } + }).binder + } + + private fun getPackageNameSuffix(packageName: String): String { + return packageName + ":" + PackageUtils.firstSignatureDigest(this, packageName) + } + + private fun getGlobalDefaultAccount(): Account? { + val name = preferences.getString(PREF_ACCOUNT_GLOBAL, null) + val accounts: Array = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) + for (account in accounts) { + if (account.name.equals(name)) return account + } + return null + } + + private fun getDefaultAccount(packageName: String?): Account? { + if (packageName == null) return getGlobalDefaultAccount() + val name = preferences.getString(PREF_ACCOUNT_PREFIX + getPackageNameSuffix(packageName), null) + if (name.isNullOrBlank()) return getGlobalDefaultAccount() + val accounts: Array = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) + for (account in accounts) { + if (account.name.equals(name)) return account + } + return getGlobalDefaultAccount() + } + + private fun setDefaultAccount(packageName: String?, account: Account?) { + if (account?.name == getDefaultAccount(packageName)?.name) return + val key = if (packageName == null) PREF_ACCOUNT_GLOBAL else (PREF_ACCOUNT_PREFIX + getPackageNameSuffix(packageName)) + val editor: SharedPreferences.Editor = preferences.edit() + if (account == null || account.name == AuthConstants.DEFAULT_ACCOUNT) { + editor.remove(key) + } else { + editor.putString(key, account.name) + } + if (packageName != null) { + for (key in preferences.all.keys) { + if (key.startsWith(PREF_PLAYER_PREFIX + getPackageNameSuffix(packageName))) { + editor.remove(key) + } + } + } + editor.apply() + } + + private fun getPackageAndAccountSuffix(packageName: String, account: Account): String { + return getPackageNameSuffix(packageName) + ":" + account.name + } + + private fun getPlayer(packageName: String, account: Account): String? { + val player = preferences.getString(PREF_PLAYER_PREFIX + getPackageAndAccountSuffix(packageName, account), null) + if (player.isNullOrBlank()) return null + return player + } + + private fun setPlayer(packageName: String, account: Account, player: String?) { + val editor: SharedPreferences.Editor = preferences.edit() + if (player.isNullOrBlank()) { + editor.remove(PREF_PLAYER_PREFIX + getPackageAndAccountSuffix(packageName, account)) + } else { + editor.putString(PREF_PLAYER_PREFIX + getPackageAndAccountSuffix(packageName, account), player) + } + editor.apply() + } + + + companion object { + + private suspend fun singleRequest(context: Context, message: Message) = suspendCoroutine { continuation -> + val connection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + val connection = this + message.replyTo = Messenger(object : Handler(Looper.myLooper() ?: Looper.getMainLooper()) { + override fun handleMessage(msg: Message) { + runCatching { continuation.resume(msg) } + runCatching { context.unbindService(connection) } + } + }) + try { + Messenger(service).send(message) + } catch (e: Exception) { + runCatching { continuation.resumeWithException(e) } + runCatching { context.unbindService(connection) } + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + runCatching { continuation.resumeWithException(RuntimeException("Disconnected")) } + } + } + val connected = context.bindService(Intent(context, GamesConfigurationService::class.java), connection, BIND_AUTO_CREATE or BIND_ABOVE_CLIENT) + if (!connected) { + runCatching { continuation.resumeWithException(RuntimeException("Connection failed")) } + runCatching { context.unbindService(connection) } + } + } + + suspend fun getDefaultAccount(context: Context, packageName: String?): Account? { + return singleRequest(context, Message.obtain().apply { + what = MSG_GET_DEFAULT_ACCOUNT + data = bundleOf( + MSG_DATA_PACKAGE_NAME to packageName + ) + }).data?.getParcelable(MSG_DATA_ACCOUNT) + } + + suspend fun setDefaultAccount(context: Context, packageName: String?, account: Account?) { + singleRequest(context, Message.obtain().apply { + what = MSG_SET_DEFAULT_ACCOUNT + data = bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account + ) + }) + } + + suspend fun getPlayer(context: Context, packageName: String, account: Account): String? { + return singleRequest(context, Message.obtain().apply { + what = MSG_GET_PLAYER + data = bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account + ) + }).data?.getString(MSG_DATA_PLAYER) + } + + suspend fun setPlayer(context: Context, packageName: String, account: Account, player: String?) { + singleRequest(context, Message.obtain().apply { + what = MSG_SET_PLAYER + data = bundleOf( + MSG_DATA_PACKAGE_NAME to packageName, + MSG_DATA_ACCOUNT to account, + MSG_DATA_PLAYER to player + ) + }) + } + + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConnectService.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConnectService.kt new file mode 100644 index 0000000000000000000000000000000000000000..55fc78ef5160f33c4ff3da7672e7e1449d0546d9 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConnectService.kt @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.games + +import android.accounts.Account +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Parcel +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.common.Scopes +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +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.games.internal.connect.GamesSignInRequest +import com.google.android.gms.games.internal.connect.GamesSignInResponse +import com.google.android.gms.games.internal.connect.IGamesConnectCallbacks +import com.google.android.gms.games.internal.connect.IGamesConnectService +import org.microg.gms.BaseService +import org.microg.gms.auth.AuthManager +import org.microg.gms.auth.AuthPrefs +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils +import org.microg.gms.utils.warnOnTransactionIssues +import java.util.UUID + +private const val TAG = "GamesConnectService" + +class GamesConnectService : BaseService(TAG, GmsService.GAMES) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) + ?: throw IllegalArgumentException("Missing package name") + callback.onPostInitCompleteWithConnectionInfo( + CommonStatusCodes.SUCCESS, + GamesConnectServiceImpl(this, lifecycle, packageName), + ConnectionInfo() + ) + } +} + +class GamesConnectServiceImpl(val context: Context, private val lifecycle: Lifecycle, val packageName: String) : IGamesConnectService.Stub(), LifecycleOwner { + + override fun getLifecycle(): Lifecycle = lifecycle + + override fun signIn(callback: IGamesConnectCallbacks?, request: GamesSignInRequest?) { + Log.d(TAG, "signIn($request)") + fun sendSignInRequired() { + val resolution = PendingIntent.getActivity(context, packageName.hashCode(), Intent(context, GamesSignInActivity::class.java).apply { + putExtra(EXTRA_GAME_PACKAGE_NAME, packageName) + putExtra(EXTRA_SCOPES, arrayOf(Scopes.GAMES_LITE)) + }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + when (request?.signInType) { + 0 -> { // Manual sign in, provide resolution + callback?.onSignIn(Status(CommonStatusCodes.SIGN_IN_REQUIRED, null, resolution), null) + } + + 1 -> { // Auto sign-in on start, don't provide resolution if not + callback?.onSignIn(Status(CommonStatusCodes.SIGN_IN_REQUIRED), null) + } + + else -> { + callback?.onSignIn(Status(CommonStatusCodes.SIGN_IN_REQUIRED), null) + } + } + } + lifecycleScope.launchWhenStarted { + try { + val account = request?.previousStepResolutionResult?.resultData?.getParcelableExtra(EXTRA_ACCOUNT) + ?: GamesConfigurationService.getDefaultAccount(context, packageName) + ?: return@launchWhenStarted sendSignInRequired() + val authManager = AuthManager(context, account.name, packageName, "oauth2:${Scopes.GAMES_LITE}") + if (!authManager.isPermitted && !AuthPrefs.isTrustGooglePermitted(context)) return@launchWhenStarted sendSignInRequired() + val result = performGamesSignIn(context, packageName, account) + if (result) { + callback?.onSignIn(Status.SUCCESS, GamesSignInResponse().apply { gameRunToken = UUID.randomUUID().toString() }) + } else { + sendSignInRequired() + } + } catch (e: Exception) { + Log.w(TAG, e) + return@launchWhenStarted sendSignInRequired() + } + } + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/GamesService.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesService.kt new file mode 100644 index 0000000000000000000000000000000000000000..6659a72de3b170849656615a8eac7ca32e5a1b5e --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesService.kt @@ -0,0 +1,574 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.games + +import android.accounts.Account +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.IBinder +import android.os.Parcel +import android.util.Log +import androidx.core.os.bundleOf +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.Scopes +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Scope +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.data.DataHolder +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.games.Player +import com.google.android.gms.games.PlayerColumns +import com.google.android.gms.games.PlayerEntity +import com.google.android.gms.games.internal.IGamesCallbacks +import com.google.android.gms.games.internal.IGamesClient +import com.google.android.gms.games.internal.IGamesService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.microg.gms.BaseService +import org.microg.gms.auth.AuthConstants +import org.microg.gms.auth.AuthManager +import org.microg.gms.auth.AuthPrefs +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "GamesService" + +class GamesService : BaseService(TAG, GmsService.GAMES) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + val packageName = PackageUtils.getAndCheckCallingPackageOrExtendedAccess(this, request.packageName) + ?: throw IllegalArgumentException("Missing package name") + + fun sendSignInRequired() { + Log.d(TAG, "Sending SIGN_IN_REQUIRED to $packageName") + callback.onPostInitCompleteWithConnectionInfo(ConnectionResult.SIGN_IN_REQUIRED, null, ConnectionInfo().apply { + params = bundleOf( + "pendingIntent" to PendingIntent.getActivity( + this@GamesService, + packageName.hashCode(), + Intent(this@GamesService, GamesSignInActivity::class.java).apply { + putExtra(EXTRA_GAME_PACKAGE_NAME, request.packageName) + putExtra(EXTRA_ACCOUNT, request.account) + putExtra(EXTRA_SCOPES, request.scopes) + }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + }) + } + + lifecycleScope.launchWhenStarted { + try { + val account = request.account.takeIf { it.name != AuthConstants.DEFAULT_ACCOUNT } + ?: GamesConfigurationService.getDefaultAccount(this@GamesService, packageName) + ?: return@launchWhenStarted sendSignInRequired() + + val scopes = (request.scopes.toSet() + Scope(Scopes.GAMES_LITE)).toList().sortedBy { it.scopeUri } + val authManager = AuthManager(this@GamesService, account.name, packageName, "oauth2:${scopes.joinToString(" ")}") + if (!authManager.isPermitted && !AuthPrefs.isTrustGooglePermitted(this@GamesService)) { + Log.d(TAG, "Not permitted to use $account for ${scopes.toList()}, sign in required") + return@launchWhenStarted sendSignInRequired() + } + + if (!performGamesSignIn(this@GamesService, packageName, account, scopes = scopes)) { + return@launchWhenStarted sendSignInRequired() + } + + val player = JSONObject(GamesConfigurationService.getPlayer(this@GamesService, packageName, account)).toPlayer() + + callback.onPostInitCompleteWithConnectionInfo( + CommonStatusCodes.SUCCESS, + GamesServiceImpl(this@GamesService, lifecycle, packageName, account, player), + ConnectionInfo() + ) + } catch (e: Exception) { + Log.w(TAG, e) + runCatching { callback.onPostInitCompleteWithConnectionInfo(ConnectionResult.INTERNAL_ERROR, null, null) } + } + } + } +} + +class GamesServiceImpl(val context: Context, private val lifecycle: Lifecycle, val packageName: String, val account: Account, val player: Player) : + IGamesService.Stub(), LifecycleOwner { + + override fun getLifecycle(): Lifecycle = lifecycle + + override fun clientDisconnecting(clientId: Long) { + Log.d(TAG, "Not yet implemented: clientDisconnecting($clientId)") + } + + override fun signOut(callbacks: IGamesCallbacks?) { + Log.d(TAG, "Not yet implemented: signOut") + lifecycleScope.launchWhenStarted { + GamesConfigurationService.setDefaultAccount(context, packageName, null) + callbacks?.onSignOutComplete() + } + } + + override fun getAppId(): String? { + Log.d(TAG, "Not yet implemented: getAppId") + return null + } + + override fun getConnectionHint(): Bundle? { + return null + } + + override fun showWelcomePopup(windowToken: IBinder?, extraArgs: Bundle?) { + runCatching { extraArgs?.keySet() } + Log.d(TAG, "Not yet implemented: showWelcomePopup($windowToken, $extraArgs)") + } + + override fun cancelPopups() { + Log.d(TAG, "Not yet implemented: cancelPopups") + } + + override fun getCurrentAccountName(): String? { + Log.d(TAG, "Not yet implemented: getCurrentAccountName") + return null + } + + override fun loadGameplayAclInternal(callbacks: IGamesCallbacks?, gameId: String?) { + Log.d(TAG, "Not yet implemented: loadGameplayAclInternal($gameId)") + } + + override fun updateGameplayAclInternal(callbacks: IGamesCallbacks?, gameId: String?, aclData: String?) { + Log.d(TAG, "Not yet implemented: updateGameplayAclInternal($gameId, $aclData)") + } + + override fun loadFAclInternal(callbacks: IGamesCallbacks?, gameId: String?) { + Log.d(TAG, "Not yet implemented: loadFAclInternal($gameId)") + } + + override fun updateFAclInternal(callbacks: IGamesCallbacks?, gameId: String?, allCirclesVisible: Boolean, circleIds: LongArray?) { + Log.d(TAG, "Not yet implemented: updateFAclInternal($gameId, $allCirclesVisible, $circleIds)") + } + + override fun getCurrentPlayerId(): String? { + Log.d(TAG, "Not yet implemented: getCurrentPlayerId") + return null + } + + override fun getCurrentPlayer(): DataHolder? { + return if (player is PlayerEntity) { + DataHolder.builder(PlayerColumns.CURRENT_PLAYER_COLUMNS.toTypedArray()).withRow(player.toContentValues()).build(CommonStatusCodes.SUCCESS) + } else { + DataHolder.builder(PlayerColumns.CURRENT_PLAYER_COLUMNS.toTypedArray()).build(CommonStatusCodes.SIGN_IN_REQUIRED) + } + } + + override fun loadPlayer(callbacks: IGamesCallbacks?, playerId: String?) { + Log.d(TAG, "Not yet implemented: loadPlayer($playerId)") + } + + override fun loadInvitablePlayers(callbacks: IGamesCallbacks?, pageSize: Int, expandCachedData: Boolean, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: loadInvitablePlayers($pageSize, $expandCachedData, $forceReload)") + } + + override fun submitScore(callbacks: IGamesCallbacks?, leaderboardId: String?, score: Long) { + Log.d(TAG, "Not yet implemented: submitScore($leaderboardId, $score)") + } + + override fun loadLeaderboards(callbacks: IGamesCallbacks?) { + Log.d(TAG, "Not yet implemented: loadLeaderboards") + } + + override fun loadLeaderboard(callbacks: IGamesCallbacks?, leaderboardId: String?) { + Log.d(TAG, "Not yet implemented: loadLeaderboard($leaderboardId)") + } + + override fun loadTopScores( + callbacks: IGamesCallbacks?, + leaderboardId: String?, + span: Int, + leaderboardCollection: Int, + maxResults: Int, + forceReload: Boolean + ) { + Log.d(TAG, "Not yet implemented: loadTopScores($leaderboardId, $span, $leaderboardCollection, $maxResults, $forceReload)") + } + + override fun loadPlayerCenteredScores( + callbacks: IGamesCallbacks?, + leaderboardId: String?, + span: Int, + leaderboardCollection: Int, + maxResults: Int, + forceReload: Boolean + ) { + Log.d(TAG, "Not yet implemented: loadPlayerCenteredScores($leaderboardId, $span, $leaderboardCollection, $maxResults, $forceReload)") + } + + override fun loadMoreScores(callbacks: IGamesCallbacks?, previousheader: Bundle?, maxResults: Int, pageDirection: Int) { + runCatching { previousheader?.keySet() } + Log.d(TAG, "Not yet implemented: loadMoreScores($previousheader, $maxResults, $pageDirection)") + } + + override fun loadAchievements(callbacks: IGamesCallbacks?) { + loadAchievementsV2(callbacks, false) + } + + override fun revealAchievement(callbacks: IGamesCallbacks?, achievementId: String?, windowToken: IBinder?, extraArgs: Bundle?) { + runCatching { extraArgs?.keySet() } + Log.d(TAG, "Not yet implemented: revealAchievement($achievementId, $windowToken, $extraArgs)") + } + + override fun unlockAchievement(callbacks: IGamesCallbacks?, achievementId: String?, windowToken: IBinder?, extraArgs: Bundle?) { + runCatching { extraArgs?.keySet() } + Log.d(TAG, "Not yet implemented: unlockAchievement($achievementId, $windowToken, $extraArgs") + } + + override fun incrementAchievement(callbacks: IGamesCallbacks?, achievementId: String?, numSteps: Int, windowToken: IBinder?, extraArgs: Bundle?) { + runCatching { extraArgs?.keySet() } + Log.d(TAG, "Not yet implemented: incrementAchievement($achievementId, $numSteps, $windowToken, $extraArgs)") + } + + override fun loadGame(callbacks: IGamesCallbacks?) { + Log.d(TAG, "Not yet implemented: loadGame") + } + + override fun loadInvitations(callbacks: IGamesCallbacks?) { + Log.d(TAG, "Not yet implemented: loadInvitations") + } + + override fun declineInvitation(invitationId: String?, invitationType: Int) { + Log.d(TAG, "Not yet implemented: declineInvitation($invitationId, $invitationType)") + } + + override fun dismissInvitation(invitationId: String?, invitationType: Int) { + Log.d(TAG, "Not yet implemented: dismissInvitation($invitationId, $invitationType)") + } + + override fun createRoom( + callbacks: IGamesCallbacks?, + processBinder: IBinder?, + variant: Int, + invitedPlayerIds: Array?, + autoMatchCriteria: Bundle?, + enableSockets: Boolean, + clientId: Long + ) { + Log.d(TAG, "Not yet implemented: createRoom($variant, $invitedPlayerIds, $autoMatchCriteria, $enableSockets, $clientId)") + } + + override fun joinRoom(callbacks: IGamesCallbacks?, processBinder: IBinder?, matchId: String?, enableSockets: Boolean, clientId: Long) { + Log.d(TAG, "Not yet implemented: joinRoom($matchId, $enableSockets, $clientId)") + } + + override fun leaveRoom(callbacks: IGamesCallbacks?, matchId: String?) { + Log.d(TAG, "Not yet implemented: leaveRoom($matchId)") + } + + override fun sendReliableMessage(callbacks: IGamesCallbacks?, messageData: ByteArray?, matchId: String?, recipientParticipantId: String?): Int { + Log.d(TAG, "Not yet implemented: sendReliableMessage($messageData, $matchId, $recipientParticipantId)") + return 0 + } + + override fun sendUnreliableMessage(messageData: ByteArray?, matchId: String?, recipientParticipantIds: Array?): Int { + Log.d(TAG, "Not yet implemented: sendUnreliableMessage($messageData, $matchId, $recipientParticipantIds)") + return 0 + } + + override fun createSocketConnection(participantId: String?): String? { + Log.d(TAG, "Not yet implemented: createSocketConnection($participantId)") + return null + } + + override fun clearNotifications(notificationTypes: Int) { + Log.d(TAG, "Not yet implemented: clearNotifications($notificationTypes)") + } + + override fun loadLeaderboardsFirstParty(callbacks: IGamesCallbacks?, gameId: String?) { + Log.d(TAG, "Not yet implemented: loadLeaderboardsFirstParty($gameId)") + } + + override fun loadLeaderboardFirstParty(callbacks: IGamesCallbacks?, gameId: String?, leaderboardId: String?) { + Log.d(TAG, "Not yet implemented: loadLeaderboardFirstParty($gameId, $leaderboardId)") + } + + override fun loadTopScoresFirstParty( + callbacks: IGamesCallbacks?, + gameId: String?, + leaderboardId: String?, + span: Int, + leaderboardCollection: Int, + maxResults: Int, + forceReload: Boolean + ) { + Log.d(TAG, "Not yet implemented: loadTopScoresFirstParty($gameId, $leaderboardId, $span, $leaderboardCollection, $maxResults, $forceReload)") + } + + override fun loadPlayerCenteredScoresFirstParty( + callbacks: IGamesCallbacks?, + gameId: String?, + leaderboardId: String?, + span: Int, + leaderboardCollection: Int, + maxResults: Int, + forceReload: Boolean + ) { + Log.d(TAG, "Not yet implemented: loadPlayerCenteredScoresFirstParty($gameId, $leaderboardId, $span, $leaderboardCollection, $maxResults, $forceReload)") + } + + override fun loadAchievementsFirstParty(callbacks: IGamesCallbacks?, playerId: String?, gameId: String?) { + Log.d(TAG, "Not yet implemented: loadAchievementsFirstParty($playerId, $gameId)") + } + + override fun loadGameFirstParty(callbacks: IGamesCallbacks?, gameId: String?) { + Log.d(TAG, "Not yet implemented: loadGameFirstParty($gameId)") + } + + override fun loadGameInstancesFirstParty(callbacks: IGamesCallbacks?, gameId: String?) { + Log.d(TAG, "Not yet implemented: loadGameInstancesFirstParty($gameId)") + } + + override fun loadGameCollectionFirstParty( + callbacks: IGamesCallbacks?, + pageSize: Int, + collectionType: Int, + expandCachedData: Boolean, + forceReload: Boolean + ) { + Log.d(TAG, "Not yet implemented: loadGameCollectionFirstParty($pageSize, $collectionType, $expandCachedData, $forceReload)") + } + + override fun loadRecentlyPlayedGamesFirstParty( + callbacks: IGamesCallbacks?, + externalPlayerId: String?, + pageSize: Int, + expandCachedData: Boolean, + forceReload: Boolean + ) { + Log.d(TAG, "Not yet implemented: loadRecentlyPlayedGamesFirstParty($externalPlayerId, $pageSize, $expandCachedData, $forceReload)") + } + + override fun loadInvitablePlayersFirstParty(callbacks: IGamesCallbacks?, pageSize: Int, expandCachedData: Boolean, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: loadInvitablePlayersFirstParty($pageSize, $expandCachedData, $forceReload)") + } + + override fun loadRecentPlayersFirstParty(callbacks: IGamesCallbacks?) { + Log.d(TAG, "Not yet implemented: loadRecentPlayersFirstParty") + } + + override fun loadCircledPlayersFirstParty(callbacks: IGamesCallbacks?, pageSize: Int, expandCachedData: Boolean, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: loadCircledPlayersFirstParty($pageSize, $expandCachedData, $forceReload)") + } + + override fun loadSuggestedPlayersFirstParty(callbacks: IGamesCallbacks?) { + Log.d(TAG, "Not yet implemented: loadSuggestedPlayersFirstParty") + } + + override fun dismissPlayerSuggestionFirstParty(playerIdToDismiss: String?) { + Log.d(TAG, "Not yet implemented: dismissPlayerSuggestionFirstParty($playerIdToDismiss)") + } + + override fun declineInvitationFirstParty(gameId: String?, invitationId: String?, invitationType: Int) { + Log.d(TAG, "Not yet implemented: declineInvitationFirstParty($gameId, $invitationId, $invitationType)") + } + + override fun loadInvitationsFirstParty(callbacks: IGamesCallbacks?, gameId: String?) { + Log.d(TAG, "Not yet implemented: loadInvitationsFirstParty($gameId)") + } + + override fun registerWaitingRoomListenerRestricted(callbacks: IGamesCallbacks?, roomId: String?): Int { + Log.d(TAG, "Not yet implemented: registerWaitingRoomListenerRestricted($roomId)") + return 0 + } + + override fun setGameMuteStatusInternal(callbacks: IGamesCallbacks?, gameId: String?, muted: Boolean) { + Log.d(TAG, "Not yet implemented: setGameMuteStatusInternal($gameId, $muted)") + } + + override fun clearNotificationsFirstParty(gameId: String?, notificationTypes: Int) { + Log.d(TAG, "Not yet implemented: clearNotificationsFirstParty($gameId, $notificationTypes)") + } + + override fun loadNotifyAclInternal(callbacks: IGamesCallbacks?) { + Log.d(TAG, "Not yet implemented: loadNotifyAclInternal") + } + + override fun updateNotifyAclInternal(callbacks: IGamesCallbacks?, aclData: String?) { + Log.d(TAG, "Not yet implemented: updateNotifyAclInternal($aclData)") + } + + override fun registerInvitationListener(callbacks: IGamesCallbacks?, clientId: Long) { + Log.d(TAG, "Not yet implemented: registerInvitationListener($clientId)") + } + + override fun unregisterInvitationListener(clientId: Long) { + Log.d(TAG, "Not yet implemented: unregisterInvitationListener($clientId)") + } + + override fun unregisterWaitingRoomListenerRestricted(roomId: String?): Int { + Log.d(TAG, "Not yet implemented: unregisterWaitingRoomListenerRestricted($roomId)") + return 0 + } + + override fun isGameMutedInternal(callbacks: IGamesCallbacks?, gameId: String?) { + Log.d(TAG, "Not yet implemented: isGameMutedInternal($gameId)") + } + + override fun loadContactSettingsInternal(callbacks: IGamesCallbacks?) { + Log.d(TAG, "Not yet implemented: loadContactSettingsInternal") + } + + override fun updateContactSettingsInternal(callbacks: IGamesCallbacks?, enableMobileNotifications: Boolean) { + Log.d(TAG, "Not yet implemented: updateContactSettingsInternal($enableMobileNotifications)") + } + + override fun getSelectedAccountForGameFirstParty(gamePackageName: String?): String? { + Log.d(TAG, "Not yet implemented: getSelectedAccountForGameFirstParty($gamePackageName)") + return null + } + + override fun updateSelectedAccountForGameFirstParty(gamePackageName: String?, accountName: String?) { + Log.d(TAG, "Not yet implemented: updateSelectedAccountForGameFirstParty($gamePackageName, $accountName)") + } + + override fun getGamesContentUriRestricted(gameId: String?): Uri? { + Log.d(TAG, "Not yet implemented: getGamesContentUriRestricted($gameId)") + return null + } + + override fun shouldUseNewPlayerNotificationsFirstParty(): Boolean { + Log.d(TAG, "Not yet implemented: shouldUseNewPlayerNotificationsFirstParty") + return false + } + + override fun setUseNewPlayerNotificationsFirstParty(newPlayerStyle: Boolean) { + Log.d(TAG, "Not yet implemented: setUseNewPlayerNotificationsFirstParty($newPlayerStyle)") + } + + override fun searchForPlayersFirstParty(callbacks: IGamesCallbacks?, query: String?, pageSize: Int, expandCachedData: Boolean, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: searchForPlayersFirstParty($query, $pageSize, $expandCachedData, $forceReload)") + } + + override fun getCurrentGame(): DataHolder? { + Log.d(TAG, "Not yet implemented: getCurrentGame") + return null + } + + override fun loadAchievementsV2(callbacks: IGamesCallbacks?, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: loadAchievementsV2($forceReload)") + callbacks?.onAchievementsLoaded(DataHolder.empty(CommonStatusCodes.SUCCESS)) + } + + override fun submitLeaderboardScore(callbacks: IGamesCallbacks?, leaderboardId: String?, score: Long, scoreTag: String?) { + Log.d(TAG, "Not yet implemented: submitLeaderboardScore($leaderboardId, $score, $scoreTag)") + } + + override fun setAchievementSteps(callbacks: IGamesCallbacks?, id: String?, numSteps: Int, windowToken: IBinder?, extras: Bundle?) { + runCatching { extras?.keySet() } + Log.d(TAG, "Not yet implemented: setAchievementSteps($id, $numSteps, $windowToken, $extras)") + } + + private fun getGamesIntent(action: String, block: Intent.() -> Unit = {}) = Intent(action).apply { + setPackage(GAMES_PACKAGE_NAME) + putExtra(EXTRA_GAME_PACKAGE_NAME, packageName) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + block() + } + + override fun getAllLeaderboardsIntent(): Intent = getGamesIntent(ACTION_VIEW_LEADERBOARDS) + + override fun getAchievementsIntent(): Intent = getGamesIntent(ACTION_VIEW_ACHIEVEMENTS) + + override fun getPlayerSearchIntent(): Intent = getGamesIntent(ACTION_PLAYER_SEARCH) + + override fun loadEvents(callbacks: IGamesCallbacks?, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: loadEvents($forceReload)") + } + + override fun incrementEvent(eventId: String?, incrementAmount: Int) { + Log.d(TAG, "Not yet implemented: incrementEvent($eventId, $incrementAmount)") + } + + override fun loadEventsById(callbacks: IGamesCallbacks?, forceReload: Boolean, eventsIds: Array?) { + Log.d(TAG, "Not yet implemented: loadEventsById($forceReload, $eventsIds)") + } + + override fun getMaxDataSize(): Int { + return 3 * 1024 * 1024 + } + + override fun getMaxCoverImageSize(): Int { + return 800 * 1024 + } + + override fun registerEventClient(callback: IGamesClient?, l: Long) { + Log.d(TAG, "Not yet implemented: registerEventClient($l)") + } + + private fun getCompareProfileIntent(playerId: String, block: Intent.() -> Unit = {}): Intent = getGamesIntent(ACTION_VIEW_PROFILE) { + putExtra(EXTRA_IS_SELF, playerId == currentPlayerId) + putExtra(EXTRA_ACCOUNT, currentAccount) + block() + } + + override fun getCompareProfileIntentForPlayer(player: PlayerEntity): Intent = getCompareProfileIntent(player.playerId) { + putExtra(EXTRA_PLAYER, player) + } + + override fun loadPlayerStats(callbacks: IGamesCallbacks?, forceReload: Boolean) { + Log.d(TAG, "Not yet implemented: loadPlayerStats($forceReload)") + } + + override fun getCurrentAccount(): Account? { + Log.d(TAG, "Not yet implemented: getCurrentAccount") + return account + } + + override fun isTelevision(): Boolean { + Log.d(TAG, "Not yet implemented: isTelevision") + return false + } + + override fun getCompareProfileIntentWithAlternativeNameHints( + otherPlayerId: String, + otherPlayerInGameName: String?, + currentPlayerInGameName: String? + ): Intent = getCompareProfileIntent(otherPlayerId) { + putExtra(EXTRA_PLAYER_ID, otherPlayerId) + putExtra(EXTRA_OTHER_PLAYER_IN_GAME_NAME, otherPlayerInGameName) + putExtra(EXTRA_SELF_IN_GAME_NAME, currentPlayerInGameName) + } + + override fun requestServerSideAccess(callbacks: IGamesCallbacks, serverClientId: String, forceRefreshToken: Boolean) { + lifecycleScope.launchWhenStarted { + try { + val serverAuthTokenResponse = withContext(Dispatchers.IO) { + val serverAuthTokenManager = AuthManager(context, account.name, packageName, "oauth2:server:client_id:${serverClientId}:api_scope:${Scopes.GAMES_LITE}") + serverAuthTokenManager.setOauth2Prompt(if (forceRefreshToken) "consent" else "auto") + serverAuthTokenManager.setItCaveatTypes("2") + serverAuthTokenManager.isPermitted = true + serverAuthTokenManager.invalidateAuthToken() + serverAuthTokenManager.requestAuth(true) + } + if (serverAuthTokenResponse.auth != null) { + callbacks.onServerAuthCode(Status(CommonStatusCodes.SUCCESS), serverAuthTokenResponse.auth) + } else { + callbacks.onServerAuthCode(Status(CommonStatusCodes.SIGN_IN_REQUIRED), null) + } + } catch (e: Exception) { + Log.w(TAG, e) + runCatching { callbacks.onServerAuthCode(Status(CommonStatusCodes.INTERNAL_ERROR), null) } + } + } + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/GamesSignInActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesSignInActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..27c99cc1f3e5cf19f6ceefdc8e0ec98d89553df2 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesSignInActivity.kt @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.games + +import android.accounts.Account +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.Gravity +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.auth.api.signin.GoogleSignInAccount +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import com.google.android.gms.auth.api.signin.internal.SignInConfiguration +import com.google.android.gms.common.api.Status +import org.microg.gms.auth.AuthConstants +import org.microg.gms.auth.signin.AuthSignInActivity +import org.microg.gms.common.Constants + +private const val TAG = "GamesSignIn" + +private const val REQUEST_CODE_GOOGLE_SIGN_IN = 200 + +class GamesSignInActivity : AppCompatActivity() { + val gamePackageName: String? + get() = intent?.getStringExtra(EXTRA_GAME_PACKAGE_NAME) + val account: Account? + get() = intent?.getParcelableExtra(EXTRA_ACCOUNT) + val popupGravity: Int + get() = intent?.getIntExtra(EXTRA_POPUP_GRAVITY, Gravity.TOP or Gravity.CENTER_HORIZONTAL) ?: (Gravity.TOP or Gravity.CENTER_HORIZONTAL) + + private val Int.px: Int get() = (this * resources.displayMetrics.density).toInt() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (gamePackageName == null || (gamePackageName != callingActivity?.packageName && callingActivity?.packageName != packageName)) return finish() + + window.setGravity(popupGravity) + startActivityForResult(Intent(this, AuthSignInActivity::class.java).apply { + putExtra("config", SignInConfiguration().apply { + packageName = gamePackageName + options = account?.name?.takeIf { it != AuthConstants.DEFAULT_ACCOUNT }?.let { + GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).setAccountName(it).build() + } ?: GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN + }) + Log.d(TAG, "Redirect to GOOGLE_SIGN_IN using $this") + }, REQUEST_CODE_GOOGLE_SIGN_IN) + } + + private suspend fun signIn(account: Account) { + Log.d(TAG, "Sign in as $account") + if (performGamesSignIn(this, gamePackageName!!, account, permitted = true)) { + GamesConfigurationService.setDefaultAccount(this, gamePackageName, account) + } + setResult(RESULT_OK, Intent().apply { + putExtra(EXTRA_ACCOUNT, account) + }) + finish() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN) { + val status = data?.extras?.also { it.classLoader = Status::class.java.classLoader }?.getParcelable("googleSignInStatus") + if (status?.isSuccess == true) { + val account = data.extras?.also { it.classLoader = GoogleSignInAccount::class.java.classLoader } + ?.getParcelable("googleSignInAccount")?.account + if (account != null) { + lifecycleScope.launchWhenStarted { + signIn(account) + } + return + } + } + finish() + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/extensions.kt new file mode 100644 index 0000000000000000000000000000000000000000..499c850f44b73bf08c520aff2fb775d5769171d4 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/games/extensions.kt @@ -0,0 +1,267 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.games + +import android.accounts.Account +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import androidx.core.content.contentValuesOf +import androidx.core.net.toUri +import com.android.volley.* +import com.android.volley.Response.success +import com.android.volley.toolbox.JsonObjectRequest +import com.android.volley.toolbox.Volley +import com.google.android.gms.common.Scopes +import com.google.android.gms.common.api.Scope +import com.google.android.gms.games.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.microg.gms.auth.AuthManager +import org.microg.gms.common.Constants +import org.microg.gms.common.Utils +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.getSettings +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + + +const val ACTION_START_1P = "com.google.android.play.games.service.START_1P" +const val ACTION_VIEW_LEADERBOARDS = "com.google.android.gms.games.VIEW_LEADERBOARDS" +const val ACTION_VIEW_ACHIEVEMENTS = "com.google.android.gms.games.VIEW_ACHIEVEMENTS" +const val ACTION_PLAYER_SEARCH = "com.google.android.gms.games.PLAYER_SEARCH" +const val ACTION_VIEW_PROFILE = "com.google.android.gms.games.VIEW_PROFILE" +const val ACTION_ADD_FRIEND = "com.google.android.gms.games.ADD_FRIEND" + +const val EXTRA_GAME_PACKAGE_NAME = "com.google.android.gms.games.GAME_PACKAGE_NAME" +const val EXTRA_GAME_ID = "com.google.android.gms.games.GAME_ID" +const val EXTRA_PLAYER = "com.google.android.gms.games.PLAYER" +const val EXTRA_PLAYER_ID = "com.google.android.gms.games.PLAYER_ID" +const val EXTRA_IS_SELF = "com.google.android.gms.games.IS_SELF" +const val EXTRA_ACCOUNT = "com.google.android.gms.games.ACCOUNT" +const val EXTRA_SCOPES = "com.google.android.gms.games.SCOPES" +const val EXTRA_POPUP_GRAVITY = "com.google.android.gms.games.key.connectingPopupGravity" +const val EXTRA_SELF_IN_GAME_NAME = "com.google.android.gms.games.EXTRA_SELF_IN_GAME_NAME" +const val EXTRA_OTHER_PLAYER_IN_GAME_NAME = "com.google.android.gms.games.EXTRA_OTHER_PLAYER_IN_GAME_NAME" + +const val GAMES_PACKAGE_NAME = "com.google.android.play.games" + +fun PlayerEntity.toContentValues(): ContentValues = contentValuesOf( + PlayerColumns.externalPlayerId to playerId, + PlayerColumns.profileName to displayName, + PlayerColumns.gamerTag to gamerTag, + PlayerColumns.realName to name, + PlayerColumns.profileIconImageUri to iconImageUri?.toString(), + PlayerColumns.profileIconImageUrl to iconImageUrl, + PlayerColumns.profileHiResImageUri to hiResImageUri?.toString(), + PlayerColumns.profileHiResImageUrl to hiResImageUrl, + PlayerColumns.bannerImageLandscapeUri to bannerImageLandscapeUri?.toString(), + PlayerColumns.bannerImageLandscapeUrl to bannerImageLandscapeUrl, + PlayerColumns.bannerImagePortraitUri to bannerImagePortraitUri?.toString(), + PlayerColumns.bannerImagePortraitUrl to bannerImagePortraitUrl, + PlayerColumns.lastUpdated to retrievedTimestamp, + PlayerColumns.isInCircles to isInCircles, + PlayerColumns.playedWithTimestamp to lastPlayedWithTimestamp, + PlayerColumns.playerTitle to title, + PlayerColumns.isProfileVisible to isProfileVisible, + PlayerColumns.hasDebugAccess to hasDebugAccess, + PlayerColumns.gamerFriendStatus to 0, + PlayerColumns.gamerFriendUpdateTimestamp to 0L, + PlayerColumns.isMuted to false, + PlayerColumns.totalUnlockedAchievements to totalUnlockedAchievement, + PlayerColumns.alwaysAutoSignIn to isAlwaysAutoSignIn, + PlayerColumns.hasAllPublicAcls to isProfileVisible, + + PlayerColumns.currentLevel to levelInfo?.currentLevel?.levelNumber, + PlayerColumns.currentLevelMinXp to levelInfo?.currentLevel?.minXp, + PlayerColumns.currentLevelMaxXp to levelInfo?.currentLevel?.maxXp, + PlayerColumns.nextLevel to levelInfo?.nextLevel?.levelNumber, + PlayerColumns.nextLevelMaxXp to levelInfo?.nextLevel?.maxXp, + PlayerColumns.lastLevelUpTimestamp to (levelInfo?.lastLevelUpTimestamp ?: -1), + PlayerColumns.currentXpTotal to (levelInfo?.currentXpTotal ?: -1L), + + PlayerColumns.mostRecentExternalGameId to mostRecentGameInfo?.gameId, + PlayerColumns.mostRecentGameName to mostRecentGameInfo?.gameName, + PlayerColumns.mostRecentActivityTimestamp to mostRecentGameInfo?.activityTimestampMillis, + PlayerColumns.mostRecentGameIconUri to mostRecentGameInfo?.gameIconImageUri?.toString(), + PlayerColumns.mostRecentGameHiResUri to mostRecentGameInfo?.gameHiResImageUri?.toString(), + PlayerColumns.mostRecentGameFeaturedUri to mostRecentGameInfo?.gameFeaturedImageUri?.toString(), + + PlayerColumns.playTogetherFriendStatus to relationshipInfo?.friendStatus, + PlayerColumns.playTogetherNickname to (relationshipInfo as? PlayerRelationshipInfoEntity)?.nickname, + PlayerColumns.playTogetherInvitationNickname to (relationshipInfo as? PlayerRelationshipInfoEntity)?.invitationNickname, + PlayerColumns.nicknameAbuseReportToken to (relationshipInfo as? PlayerRelationshipInfoEntity)?.nicknameAbuseReportToken, + + PlayerColumns.friendsListVisibility to currentPlayerInfo?.friendsListVisibilityStatus +) + +fun JSONObject.toPlayer() = PlayerEntity( + getString("playerId"), + getString("displayName"), + optString("avatarImageUrl").takeIf { it.isNotBlank() }?.toUri(), + optString("avatarImageUrl").takeIf { it.isNotBlank() }?.toUri(), + System.currentTimeMillis(), + 0, 0, + optString("avatarImageUrl").takeIf { it.isNotBlank() }, + optString("avatarImageUrl").takeIf { it.isNotBlank() }, + getString("title"), + null, + getJSONObject("experienceInfo")?.let { + PlayerLevelInfo( + it.optLong("currentExperiencePoints"), + 0, + it.getJSONObject("currentLevel")?.let { + PlayerLevel(it.getInt("level"), it.optLong("minExperiencePoints"), it.optLong("maxExperiencePoints")) + }, + it.getJSONObject("nextLevel")?.let { + PlayerLevel(it.getInt("level"), it.optLong("minExperiencePoints"), it.optLong("maxExperiencePoints")) + } + ) + }, + optJSONObject("profileSettings")?.optBoolean("profileVisible") ?: false, + false, + null, null, + optString("bannerUrlLandscape").takeIf { it.isNotBlank() }?.toUri(), + optString("bannerUrlLandscape").takeIf { it.isNotBlank() }, + optString("bannerUrlPortrait").takeIf { it.isNotBlank() }?.toUri(), + optString("bannerUrlPortrait").takeIf { it.isNotBlank() }, + 0, null, + optJSONObject("profileSettings")?.optString("friendsListVisibility")?.takeIf { it.isNotBlank() }?.let { + CurrentPlayerInfoEntity( + when (it) { + "VISIBLE" -> Player.FriendsListVisibilityStatus.VISIBLE + "REQUEST_REQUIRED" -> Player.FriendsListVisibilityStatus.REQUEST_REQUIRED + "FEATURE_UNAVAILABLE" -> Player.FriendsListVisibilityStatus.FEATURE_UNAVAILABLE + else -> Player.FriendsListVisibilityStatus.UNKNOWN + } + ) + }, + false, + null +) + +suspend fun registerForGames(context: Context, account: Account, queue: RequestQueue = Volley.newRequestQueue(context)) { + val authManager = AuthManager(context, account.name, Constants.GMS_PACKAGE_NAME, "oauth2:${Scopes.GAMES_FIRSTPARTY}") + authManager.setOauth2Foreground("1") + val authToken = withContext(Dispatchers.IO) { authManager.requestAuth(false).auth } + val androidId = getSettings(context, CheckIn.getContentUri(context), arrayOf(CheckIn.ANDROID_ID)) { cursor: Cursor -> cursor.getLong(0) } + val result = suspendCoroutine { continuation -> + queue.add( + object : JsonObjectRequest( + "https://www.googleapis.com/games/v1whitelisted/players/me/profilesettings?requestRandomGamerTag=true&language=${Utils.getLocale(context)}", + { continuation.resume(it) }, + { continuation.resumeWithException(RuntimeException(it)) }) { + override fun getHeaders(): MutableMap { + return mutableMapOf( + "Authorization" to "OAuth $authToken", + "X-Device-ID" to androidId.toString(16) + ) + } + } + ) + } + suspendCoroutine { continuation -> + queue.add( + object : JsonObjectRequest( + Method.PUT, + "https://www.googleapis.com/games/v1whitelisted/players/me/profilesettings?language=${Utils.getLocale(context)}", + JSONObject().apply { + put("alwaysAutoSignIn", false) + put("autoSignIn", false) + put("gamerTagIsDefault", true) + put("gamerTagIsExplicitlySet", false) + put("gamesLitePlayerStatsEnabled", false) + put("profileDiscoverableViaGoogleAccount", false) + put("profileVisibilityWasChosenByPlayer", false) + put("profileVisible", false) + put("gamerTag", result.getString("gamerTag")) + if (result.has("stockGamerAvatarUrl")) put("stockGamerAvatarUrl", result.getString("stockGamerAvatarUrl")) + }, + { continuation.resume(it) }, + { continuation.resumeWithException(RuntimeException(it)) }) { + override fun getHeaders(): MutableMap { + return mutableMapOf( + "Content-Type" to "application/json; charset=utf-8", + "Authorization" to "OAuth $authToken", + "X-Device-ID" to androidId.toString(16) + ) + } + } + ) + } +} + +suspend fun performGamesSignIn( + context: Context, + packageName: String, + account: Account, + permitted: Boolean = false, + scopes: List = emptyList(), + queue: RequestQueue = Volley.newRequestQueue(context) +): Boolean { + val scopes = (scopes.toSet() + Scope(Scopes.GAMES_LITE)).toList().sortedBy { it.scopeUri } + val authManager = AuthManager(context, account.name, packageName, "oauth2:${scopes.joinToString(" ")}") + if (scopes.size == 1) authManager.setItCaveatTypes("2") + if (permitted) authManager.isPermitted = true + val authResponse = withContext(Dispatchers.IO) { authManager.requestAuth(true) } + if (authResponse.auth == null) return false + if (authResponse.issueAdvice != "stored" || GamesConfigurationService.getPlayer(context, packageName, account) == null) { + suspend fun fetchSelfPlayer() = suspendCoroutine { continuation -> + queue.add( + object : JsonObjectRequest( + "https://www.googleapis.com/games/v1/players/me", + { continuation.resume(it) }, + { continuation.resumeWithException(it) }) { + override fun getHeaders(): MutableMap { + return mutableMapOf( + "Authorization" to "OAuth ${authResponse.auth}" + ) + } + } + ) + } + + val result = try { + fetchSelfPlayer() + } catch (e: Exception) { + if (e is VolleyError && e.networkResponse?.statusCode == 404) { + registerForGames(context, account, queue) + fetchSelfPlayer() + } else { + throw e + } + } + GamesConfigurationService.setPlayer(context, packageName, account, result.toString()) + if (packageName != GAMES_PACKAGE_NAME) { + try { + suspendCoroutine { continuation -> + queue.add(object : Request(Method.POST, "https://www.googleapis.com/games/v1/applications/played", { + continuation.resumeWithException(it) + }) { + override fun parseNetworkResponse(response: NetworkResponse): Response { + if (response.statusCode == 204) return success(Unit, null) + return Response.error(VolleyError(response)) + } + + override fun deliverResponse(response: Unit) { + continuation.resume(response) + } + + override fun getHeaders(): MutableMap { + return mutableMapOf( + "Authorization" to "OAuth ${authResponse.auth}" + ) + } + }) + } + } catch (ignored: Exception) { + } + } + } + return true +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/signin/SignInService.kt b/play-services-core/src/main/kotlin/org/microg/gms/signin/SignInService.kt index b8ecaf8a89836baf2bbccced1eb4cce1d96ecaba..0880213bba3c04038c5f36fcc66ce6ac31714bda 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/signin/SignInService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/signin/SignInService.kt @@ -6,11 +6,17 @@ package org.microg.gms.signin import android.accounts.Account +import android.accounts.AccountManager +import android.content.Context import android.os.Bundle import android.os.Parcel import android.util.Log +import androidx.core.content.getSystemService +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Scope import com.google.android.gms.common.internal.* import com.google.android.gms.signin.internal.* import org.microg.gms.BaseService @@ -24,12 +30,15 @@ class SignInService : BaseService(TAG, GmsService.SIGN_IN) { override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) ?: throw IllegalArgumentException("Missing package name") - val binder = SignInServiceImpl().asBinder() + val binder = SignInServiceImpl(this, lifecycle, packageName, request.scopes).asBinder() callback.onPostInitComplete(CommonStatusCodes.SUCCESS, binder, Bundle()) } } -class SignInServiceImpl : ISignInService.Stub() { +class SignInServiceImpl(val context: Context, private val lifecycle: Lifecycle, val packageName: String, val scopes: Array) : ISignInService.Stub(), + LifecycleOwner { + override fun getLifecycle(): Lifecycle = lifecycle + override fun clearAccountFromSessionStore(sessionId: Int) { Log.d(TAG, "Not yet implemented: clearAccountFromSessionStore $sessionId") } @@ -51,13 +60,54 @@ class SignInServiceImpl : ISignInService.Stub() { } override fun signIn(request: SignInRequest?, callbacks: ISignInCallbacks?) { - Log.d(TAG, "Not yet implemented: signIn $request") - callbacks?.onSignIn(SignInResponse().apply { - connectionResult = ConnectionResult(ConnectionResult.INTERNAL_ERROR) - response = ResolveAccountResponse().apply { - connectionResult = ConnectionResult(ConnectionResult.INTERNAL_ERROR) - } - }) + Log.d(TAG, "signIn($request)") + val account = request?.request?.account + val result = if (account == null || context.getSystemService()?.getAccountsByType(account.type)?.contains(account) != true) + ConnectionResult(ConnectionResult.SIGN_IN_REQUIRED) else ConnectionResult(ConnectionResult.SUCCESS) + runCatching { + callbacks?.onSignIn(SignInResponse().apply { + connectionResult = result + response = ResolveAccountResponse().apply { + connectionResult = result + if (account != null) { + accountAccessor = object : IAccountAccessor.Stub() { + override fun getAccount(): Account { + return account + } + } + } + } + }) + } +// fun sendError() { +// runCatching { +// callbacks?.onSignIn(SignInResponse().apply { +// connectionResult = ConnectionResult(ConnectionResult.INTERNAL_ERROR) +// response = ResolveAccountResponse().apply { +// connectionResult = ConnectionResult(ConnectionResult.INTERNAL_ERROR) +// } +// }) +// } +// } +// Log.d(TAG, "Not yet implemented: signIn $request with $scopes") +// val account = request?.request?.account ?: return sendError() +// val authManager = AuthManager(context, account.name, packageName, "oauth2:${scopes.joinToString(" ") { it.scopeUri }}") +// authManager.setItCaveatTypes("2") +// if (!authManager.isPermitted && !AuthPrefs.isTrustGooglePermitted(context)) return sendError() +// lifecycleScope.launchWhenStarted { +// val authResponse = withContext(Dispatchers.IO) { +// authManager.requestAuth(true) +// } +// if (authResponse.auths == null) return@launchWhenStarted sendError() +// runCatching { +// callbacks?.onSignIn(SignInResponse().apply { +// connectionResult = ConnectionResult(ConnectionResult.SUCCESS) +// response = ResolveAccountResponse().apply { +// connectionResult = ConnectionResult(ConnectionResult.SUCCESS) +// } +// }) +// } +// } } override fun setGamesHasBeenGreeted(hasGreeted: Boolean) { @@ -84,5 +134,6 @@ class SignInServiceImpl : ISignInService.Stub() { Log.d(TAG, "Not yet implemented: resolveAccount") } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { 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) } } \ 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 d52c4d0549ac45282900fe3349a623f150bef424..6cabe46bf28b6a39634e5fb2f986297fb2f2a8a9 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 @@ -5,17 +5,19 @@ package org.microg.gms.ui -import android.content.Context import android.os.Bundle +import android.util.Log import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.Preference +import androidx.preference.PreferenceCategory import com.google.android.gms.R 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.safetynet.SafetyNetPreferences +import org.microg.gms.ui.settings.SettingsProvider +import org.microg.gms.ui.settings.getAllSettingsProviders import org.microg.tools.ui.ResourceSettingsFragment import android.content.Intent import android.net.Uri @@ -23,6 +25,8 @@ import com.google.android.gms.BuildConfig import java.util.Locale class SettingsFragment : ResourceSettingsFragment() { + private val createdPreferences = mutableListOf() + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { super.onCreatePreferences(savedInstanceState, rootKey) @@ -42,32 +46,49 @@ class SettingsFragment : ResourceSettingsFragment() { findNavController().navigate(requireContext(), R.id.openLocationSettings) true } - findPreference(PREF_EXPOSURE)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(requireContext(), NearbyPreferencesIntegration.exposureNotificationNavigationId) + findPreference(PREF_ABOUT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openAbout) true } + findPreference(PREF_ABOUT)!!.summary = getString(org.microg.tools.ui.R.string.about_version_str, AboutFragment.getSelfVersion(context)) - findPreference(PREF_EXPOSURE_INSTALL)?.apply { - onPreferenceClickListener = Preference.OnPreferenceClickListener { - openBrowserAt(EXPOSURE_INSTALL_DOC_URL) - true - } + for (entry in getAllSettingsProviders(requireContext()).flatMap { it.getEntriesStatic(requireContext()) }) { + entry.createPreference() + } + } - if (BuildConfig.FLAVOR.lowercase(Locale.ROOT).contains("withnearby")) { - isVisible = false + 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 + } - - findPreference(PREF_ABOUT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(requireContext(), R.id.openAbout) + 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 } - findPreference(PREF_ABOUT)!!.summary = getString(org.microg.tools.ui.R.string.about_version_str, AboutFragment.getSelfVersion(context)) - - findPreference(PREF_EXPOSURE)?.isVisible = NearbyPreferencesIntegration.isAvailable - findPreference(PREF_EXPOSURE)?.icon = NearbyPreferencesIntegration.getIcon(requireContext()) - findPreference(PREF_EXPOSURE)?.summary = NearbyPreferencesIntegration.getExposurePreferenceSummary(requireContext()) + return this } private fun openBrowserAt(url: String) { @@ -83,13 +104,25 @@ class SettingsFragment : ResourceSettingsFragment() { val database = GcmDatabase(context) val regCount = database.registrationList.size database.close() - findPreference(PREF_GCM)!!.summary = context.getString(R.string.service_status_enabled_short) + " - " + context.resources.getQuantityString(R.plurals.gcm_registered_apps_counter, regCount, regCount) + findPreference(PREF_GCM)!!.summary = context.getString(org.microg.gms.base.core.R.string.service_status_enabled_short) + " - " + context.resources.getQuantityString(R.plurals.gcm_registered_apps_counter, regCount, regCount) } else { - findPreference(PREF_GCM)!!.setSummary(R.string.service_status_disabled_short) + findPreference(PREF_GCM)!!.setSummary(org.microg.gms.base.core.R.string.service_status_disabled_short) } - findPreference(PREF_CHECKIN)!!.setSummary(if (CheckinPreferences.isEnabled(requireContext())) R.string.service_status_enabled_short else R.string.service_status_disabled_short) - findPreference(PREF_SNET)!!.setSummary(if (SafetyNetPreferences.isEnabled(requireContext())) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_CHECKIN)!!.setSummary(if (CheckinPreferences.isEnabled(requireContext())) org.microg.gms.base.core.R.string.service_status_enabled_short else org.microg.gms.base.core.R.string.service_status_disabled_short) + findPreference(PREF_SNET)!!.setSummary(if (SafetyNetPreferences.isEnabled(requireContext())) org.microg.gms.base.core.R.string.service_status_enabled_short else org.microg.gms.base.core.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() + } + } } companion object { @@ -98,11 +131,6 @@ class SettingsFragment : ResourceSettingsFragment() { const val PREF_SNET = "pref_snet" const val PREF_LOCATION = "pref_location" const val PREF_CHECKIN = "pref_checkin" - const val PREF_EXPOSURE = "pref_exposure" - const val PREF_EXPOSURE_INSTALL = "pref_exposure_install" - const val EXPOSURE_INSTALL_DOC_URL = - "https://doc.e.foundation/support-topics/micro-g#how-to-update-the-microg-exposure-notification-framework" - } init { diff --git a/play-services-core/src/main/res/drawable/ic_add_account_alt.xml b/play-services-core/src/main/res/drawable/ic_add_account_alt.xml new file mode 100644 index 0000000000000000000000000000000000000000..e9eef39441a43b39dbea5085cb1d2afa8cf41212 --- /dev/null +++ b/play-services-core/src/main/res/drawable/ic_add_account_alt.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/play-services-core/src/main/res/layout/signin_account_row.xml b/play-services-core/src/main/res/layout/signin_account_row.xml new file mode 100644 index 0000000000000000000000000000000000000000..c5e563e4217ac4fa3b64a7b748ac90fe51383fe2 --- /dev/null +++ b/play-services-core/src/main/res/layout/signin_account_row.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/layout/signin_confirm.xml b/play-services-core/src/main/res/layout/signin_confirm.xml new file mode 100644 index 0000000000000000000000000000000000000000..9e9003a60f716a326424b85878e2db7397a9ebb0 --- /dev/null +++ b/play-services-core/src/main/res/layout/signin_confirm.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +