From 0338543570246870f7bb4b982ab3f12505f9b666 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 14 Aug 2023 09:50:28 +0200 Subject: [PATCH 1/4] Add Emulator profiles --- .../res/xml/profile_sdk_gphone64_arm64_33.xml | 37 +++++++++++++++++++ .../xml/profile_sdk_gphone64_x86_64_33.xml | 37 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml create mode 100644 play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml diff --git a/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml b/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml new file mode 100644 index 0000000000..62d223c5d0 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_sdk_gphone64_arm64_33.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml b/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml new file mode 100644 index 0000000000..b616c36543 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_sdk_gphone64_x86_64_33.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e6a443ac16359052f6b8231ddc2c5139b95e209b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 14 Aug 2023 15:44:51 +0200 Subject: [PATCH 2/4] Move SafeParcel into play-services-basement - Cache reflection-retrieved data to boost performance - Foundation for compile-time generated Safe-Parcelable Creators (via annotation processor) --- build.gradle | 10 - play-services-basement/build.gradle | 3 - .../safeparcel/AbstractSafeParcelable.java | 24 + .../internal/safeparcel/SafeParcelReader.java | 328 +++++++++++ .../internal/safeparcel/SafeParcelWriter.java | 384 +++++++++++++ .../internal/safeparcel/SafeParcelable.java | 47 ++ .../SafeParcelableCreatorAndWriter.java | 13 + .../safeparcel/SafeParcelableSerializer.java | 30 + .../microg/safeparcel/AutoSafeParcelable.java | 48 ++ ...flectedSafeParcelableCreatorAndWriter.java | 36 ++ .../safeparcel/SafeParcelReflectionUtil.java | 524 ++++++++++++++++++ .../org/microg/safeparcel/SafeParceled.java | 26 + .../microg/gms/ui/PlacePickerActivity.java | 8 +- ...AuthenticationExtensionsClientOutputs.java | 6 +- .../AuthenticatorAssertionResponse.java | 6 +- .../AuthenticatorAttestationResponse.java | 6 +- .../common/AuthenticatorErrorResponse.java | 6 +- ...serPublicKeyCredentialCreationOptions.java | 4 +- ...wserPublicKeyCredentialRequestOptions.java | 4 +- .../fido2/api/common/PublicKeyCredential.java | 6 +- .../PublicKeyCredentialCreationOptions.java | 4 +- .../PublicKeyCredentialRequestOptions.java | 4 +- .../fido/fido2/api/common/RequestOptions.java | 4 +- .../location/manager/LastLocationCapsule.kt | 8 +- .../gms/location/ui/LocationAppFragment.kt | 11 +- .../location/ActivityRecognitionResult.java | 4 +- .../location/ActivityTransitionRequest.java | 4 +- .../location/ActivityTransitionResult.java | 4 +- .../android/gms/location/GeofencingEvent.java | 4 +- .../gms/location/LocationSettingsStates.java | 5 +- play-services-maps/build.gradle | 2 + .../google/android/gms/maps/model/LatLng.java | 33 +- safe-parcel-processor/build.gradle | 1 + .../src/main/AndroidManifest.xml | 6 + .../microg/safeparcel/SafeParcelProcessor.kt | 278 ++++++++++ .../javax.annotation.processing.Processor | 6 + settings.gradle | 9 +- 37 files changed, 1832 insertions(+), 74 deletions(-) create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java create mode 100644 play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java create mode 100644 play-services-basement/src/main/java/org/microg/safeparcel/AutoSafeParcelable.java create mode 100644 play-services-basement/src/main/java/org/microg/safeparcel/ReflectedSafeParcelableCreatorAndWriter.java create mode 100644 play-services-basement/src/main/java/org/microg/safeparcel/SafeParcelReflectionUtil.java create mode 100644 play-services-basement/src/main/java/org/microg/safeparcel/SafeParceled.java create mode 100644 safe-parcel-processor/build.gradle create mode 100644 safe-parcel-processor/src/main/AndroidManifest.xml create mode 100644 safe-parcel-processor/src/main/kotlin/org/microg/safeparcel/SafeParcelProcessor.kt create mode 100644 safe-parcel-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor diff --git a/build.gradle b/build.gradle index 38c847f4c6..6c29810cb4 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,6 @@ buildscript { ext.cronetVersion = '102.5005.125' - ext.safeParcelVersion = '1.7.1' ext.wearableVersion = '0.1.1' ext.kotlinVersion = '1.7.10' @@ -108,14 +107,5 @@ subprojects { mavenCentral() google() } - afterEvaluate { - // Temporary hack for Android Studio - if (project.plugins.hasPlugin('com.android.base')) { - dependencies { - compileOnly "org.microg:safe-parcel:$safeParcelVersion" - testCompileOnly "org.microg:safe-parcel:$safeParcelVersion" - } - } - } } diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index 261478b288..6a52857805 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -17,11 +17,8 @@ apply plugin: 'com.android.library' apply plugin: 'maven-publish' apply plugin: 'signing' -apply plugin: 'com.kezong.fat-aar' dependencies { - embed "org.microg:safe-parcel:$safeParcelVersion" - // Dependencies from play-services-basement:18.1.0 api "androidx.collection:collection:1.0.0" api "androidx.core:core:1.2.0" diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java new file mode 100644 index 0000000000..ec7d84d544 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/AbstractSafeParcelable.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +public abstract class AbstractSafeParcelable implements SafeParcelable { + + @SuppressWarnings("unchecked") + public static SafeParcelableCreatorAndWriter findCreator(java.lang.Class tClass) { + String creatorClassName = tClass.getName() + "$000Creator"; + try { + return (SafeParcelableCreatorAndWriter) java.lang.Class.forName(creatorClassName).newInstance(); + } catch (Exception e) { + throw new RuntimeException("No Creator found for " + tClass.getName(), e); + } + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java new file mode 100644 index 0000000000..226f50e8d9 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelReader.java @@ -0,0 +1,328 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.HashMap; + +@SuppressWarnings("MagicNumber") +public final class SafeParcelReader { + + private SafeParcelReader() { + } + + @Deprecated + public static int halfOf(int i) { + return i & 0xFFFF; + } + + public static int getFieldId(int header) { + return header & 0xFFFF; + } + + @Deprecated + public static int readSingleInt(Parcel parcel) { + return parcel.readInt(); + } + + public static int readHeader(Parcel parcel) { + return parcel.readInt(); + } + + private static int readSize(Parcel parcel, int header) { + if ((header & 0xFFFF0000) != 0xFFFF0000) + return header >> 16 & 0xFFFF; + return parcel.readInt(); + } + + private static void readExpectedSize(Parcel parcel, int header, int expectedSize) { + int i = readSize(parcel, header); + if (i != expectedSize) + throw new ReadException("Expected size " + expectedSize + " got " + i + " (0x" + Integer.toHexString(i) + ")", parcel); + } + + @Deprecated + public static int readStart(Parcel parcel) { + return readObjectHeader(parcel); + } + + public static int readObjectHeader(Parcel parcel) { + int header = readHeader(parcel); + int size = readSize(parcel, header); + int start = parcel.dataPosition(); + if (getFieldId(header) != SafeParcelable.SAFE_PARCEL_OBJECT_MAGIC) + throw new ReadException("Expected object header. Got 0x" + Integer.toHexString(header), parcel); + int end = start + size; + if ((end < start) || (end > parcel.dataSize())) + throw new ReadException("Size read is invalid start=" + start + " end=" + end, parcel); + return end; + } + + public static int readInt(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readInt(); + } + + public static byte readByte(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return (byte) parcel.readInt(); + } + + public static short readShort(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return (short) parcel.readInt(); + } + + public static boolean readBool(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readInt() != 0; + } + + public static long readLong(Parcel parcel, int header) { + readExpectedSize(parcel, header, 8); + return parcel.readLong(); + } + + public static float readFloat(Parcel parcel, int header) { + readExpectedSize(parcel, header, 4); + return parcel.readFloat(); + } + + public static double readDouble(Parcel parcel, int header) { + readExpectedSize(parcel, header, 8); + return parcel.readDouble(); + } + + public static String readString(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + String string = parcel.readString(); + parcel.setDataPosition(start + size); + return string; + } + + public static IBinder readBinder(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + IBinder binder = parcel.readStrongBinder(); + parcel.setDataPosition(start + size); + return binder; + } + + public static T readParcelable(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + T t = creator.createFromParcel(parcel); + parcel.setDataPosition(start + size); + return t; + } + + public static ArrayList readList(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.readArrayList(classLoader); + parcel.setDataPosition(start + size); + return list; + } + + public static HashMap readMap(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + HashMap map = parcel.readHashMap(classLoader); + parcel.setDataPosition(start + size); + return map; + } + + public static ArrayList readParcelableList(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.createTypedArrayList(creator); + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readStringList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + ArrayList list = parcel.createStringArrayList(); + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readIntegerList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readInt()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readLongList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readLong()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readFloatList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readFloat()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readDoubleList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readDouble()); + } + parcel.setDataPosition(start + size); + return list; + } + + public static ArrayList readBooleanList(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(parcel.readInt() != 0); + } + parcel.setDataPosition(start + size); + return list; + } + + public static T[] readParcelableArray(Parcel parcel, int header, Parcelable.Creator creator) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + T[] arr = parcel.createTypedArray(creator); + parcel.setDataPosition(start + size); + return arr; + } + + public static String[] readStringArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + String[] arr = parcel.createStringArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static byte[] readByteArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + byte[] arr = parcel.createByteArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static byte[][] readByteArrayArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int length = parcel.readInt(); + byte[][] arr = new byte[length][]; + for (int i = 0; i < length; i++) { + arr[i] = parcel.createByteArray(); + } + parcel.setDataPosition(start + size); + return arr; + } + + public static float[] readFloatArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + float[] arr = parcel.createFloatArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static int[] readIntArray(Parcel parcel, int header) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + int[] arr = parcel.createIntArray(); + parcel.setDataPosition(start + size); + return arr; + } + + public static Bundle readBundle(Parcel parcel, int header, ClassLoader classLoader) { + int size = readSize(parcel, header); + if (size == 0) + return null; + int start = parcel.dataPosition(); + Bundle bundle = parcel.readBundle(classLoader); + parcel.setDataPosition(start + size); + return bundle; + } + + public static void skip(Parcel parcel, int header) { + int size = readSize(parcel, header); + parcel.setDataPosition(parcel.dataPosition() + size); + } + + public static class ReadException extends RuntimeException { + public ReadException(String message, Parcel parcel) { + super(message); + } + } +} \ No newline at end of file diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java new file mode 100644 index 0000000000..b2f9611644 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelWriter.java @@ -0,0 +1,384 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; +import java.util.Map; + +@SuppressWarnings("MagicNumber") +public final class SafeParcelWriter { + + private SafeParcelWriter() { + } + + private static void writeHeader(Parcel parcel, int fieldId, int size) { + if (size >= 0xFFFF) { + parcel.writeInt(0xFFFF0000 | fieldId); + parcel.writeInt(size); + } else { + parcel.writeInt(size << 16 | fieldId); + } + } + + @Deprecated + public static int writeStart(Parcel parcel) { + return writeObjectHeader(parcel); + } + + public static int writeObjectHeader(Parcel parcel) { + writeHeader(parcel, SafeParcelable.SAFE_PARCEL_OBJECT_MAGIC, 0xFFFF); + return parcel.dataPosition(); + } + + private static int writeObjectHeader(Parcel parcel, int fieldId) { + writeHeader(parcel, fieldId, 0xFFFF); + return parcel.dataPosition(); + } + + @Deprecated + public static void writeEnd(Parcel parcel, int start) { + finishObjectHeader(parcel, start); + } + + public static void finishObjectHeader(Parcel parcel, int start) { + int end = parcel.dataPosition(); + int length = end - start; + parcel.setDataPosition(start - 4); + parcel.writeInt(length); + parcel.setDataPosition(end); + } + + public static void write(Parcel parcel, int fieldId, Boolean val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val ? 1 : 0); + } + + public static void write(Parcel parcel, int fieldId, Byte val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Short val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Integer val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int fieldId, Long val) { + if (val == null) return; + writeHeader(parcel, fieldId, 8); + parcel.writeLong(val); + } + + public static void write(Parcel parcel, int fieldId, Float val) { + if (val == null) return; + writeHeader(parcel, fieldId, 4); + parcel.writeFloat(val); + } + + public static void write(Parcel parcel, int fieldId, Double val) { + if (val == null) return; + writeHeader(parcel, fieldId, 8); + parcel.writeDouble(val); + } + + public static void write(Parcel parcel, int fieldId, String val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeString(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Parcelable val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + val.writeToParcel(parcel, flags); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Bundle val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeBundle(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, byte[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeByteArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, byte[][] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.length); + for (byte[] arr : val) { + parcel.writeByteArray(arr); + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, float[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeFloatArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, int[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeIntArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, String[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStringArray(val); + finishObjectHeader(parcel, start); + } + } + + public static void writeStringList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStringList(val); + finishObjectHeader(parcel, start); + } + } + + public static void writeIntegerList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Integer i : val) { + parcel.writeInt(i); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeLongList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Long l : val) { + parcel.writeLong(l); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeFloatList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Float f : val) { + parcel.writeFloat(f); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeDoubleList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Double d : val) { + parcel.writeDouble(d); + } + finishObjectHeader(parcel, start); + } + } + + public static void writeBooleanList(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (Boolean b : val) { + parcel.writeInt(b ? 1 : 0); + } + finishObjectHeader(parcel, start); + } + } + + private static void writeArrayPart(Parcel parcel, T val, int flags) { + int before = parcel.dataPosition(); + parcel.writeInt(1); + int start = parcel.dataPosition(); + val.writeToParcel(parcel, flags); + int end = parcel.dataPosition(); + parcel.setDataPosition(before); + parcel.writeInt(end - start); + parcel.setDataPosition(end); + } + + public static void write(Parcel parcel, int fieldId, T[] val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.length); + for (T t : val) { + if (t == null) { + parcel.writeInt(0); + } else { + writeArrayPart(parcel, t, flags); + } + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, List val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeInt(val.size()); + for (T t : val) { + if (t == null) { + parcel.writeInt(0); + } else { + writeArrayPart(parcel, t, flags); + } + } + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Parcel val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.appendFrom(val, 0, val.dataSize()); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeList(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, Map val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeMap(val); + finishObjectHeader(parcel, start); + } + } + + public static void write(Parcel parcel, int fieldId, IBinder val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeHeader(parcel, fieldId, 0); + } + } else { + int start = writeObjectHeader(parcel, fieldId); + parcel.writeStrongBinder(val); + finishObjectHeader(parcel, start); + } + } + +} \ No newline at end of file diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java new file mode 100644 index 0000000000..9583d01751 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelable.java @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcelable; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public interface SafeParcelable extends Parcelable { + int SAFE_PARCEL_OBJECT_MAGIC = 0x4F45; + + @Target(ElementType.TYPE) + @interface Class { + } + + @Target(ElementType.CONSTRUCTOR) + @interface Constructor { + + } + + @Target(ElementType.PARAMETER) + @interface Param { + int value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface Field { + int value(); + + boolean mayNull() default false; + + java.lang.Class subClass() default SafeParcelable.class; + + boolean useValueParcel() default false; + + boolean useDirectList() default false; + + long versionCode() default -1; + } +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java new file mode 100644 index 0000000000..6afaa9c6a4 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableCreatorAndWriter.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcel; +import android.os.Parcelable; + +public interface SafeParcelableCreatorAndWriter extends Parcelable.Creator { + void writeToParcel(T object, Parcel parcel, int flags); +} diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java new file mode 100644 index 0000000000..66b8b4d580 --- /dev/null +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/safeparcel/SafeParcelableSerializer.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.common.internal.safeparcel; + +import android.os.Parcel; +import android.os.Parcelable; + +public class SafeParcelableSerializer { + public static T deserializeFromBytes(byte[] bytes, Parcelable.Creator tCreator) { + if (bytes == null) return null; + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(bytes, 0, bytes.length); + parcel.setDataPosition(0); + T parcelable = tCreator.createFromParcel(parcel); + parcel.recycle(); + return parcelable; + } + + public static byte[] serializeToBytes(T parcelable) { + if (parcelable == null) return null; + Parcel parcel = Parcel.obtain(); + parcelable.writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + return bytes; + } +} diff --git a/play-services-basement/src/main/java/org/microg/safeparcel/AutoSafeParcelable.java b/play-services-basement/src/main/java/org/microg/safeparcel/AutoSafeParcelable.java new file mode 100644 index 0000000000..09e0ccbf0a --- /dev/null +++ b/play-services-basement/src/main/java/org/microg/safeparcel/AutoSafeParcelable.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.safeparcel; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +public abstract class AutoSafeParcelable extends AbstractSafeParcelable { + private static final String TAG = "SafeParcel"; + + @SuppressWarnings("unchecked") + @Override + public void writeToParcel(Parcel out, int flags) { + Creator creator = SafeParcelReflectionUtil.getCreator(this.getClass()); + if (creator instanceof SafeParcelableCreatorAndWriter) { + ((SafeParcelableCreatorAndWriter) (SafeParcelableCreatorAndWriter) creator).writeToParcel(this, out, flags); + } else { + Log.w(TAG, "AutoSafeParcelable is not using SafeParcelableCreatorAndWriter"); + SafeParcelReflectionUtil.writeObject(this, out, flags); + } + } + + @SuppressWarnings("unchecked") + public static SafeParcelableCreatorAndWriter findCreator(java.lang.Class tClass) { + try { + return AbstractSafeParcelable.findCreator(tClass); + } catch (Exception e) { + if (AutoSafeParcelable.class.isAssignableFrom(tClass)) { + return (SafeParcelableCreatorAndWriter) new AutoCreator<>((java.lang.Class) tClass); + } else { + throw new RuntimeException("AutoSafeParcelable.findCreator() invoked with non-AutoSafeParcelable"); + } + } + } + + @Deprecated + public static class AutoCreator extends ReflectedSafeParcelableCreatorAndWriter { + public AutoCreator(java.lang.Class tClass) { + super(tClass); + } + } +} \ No newline at end of file diff --git a/play-services-basement/src/main/java/org/microg/safeparcel/ReflectedSafeParcelableCreatorAndWriter.java b/play-services-basement/src/main/java/org/microg/safeparcel/ReflectedSafeParcelableCreatorAndWriter.java new file mode 100644 index 0000000000..5a32b3fe3e --- /dev/null +++ b/play-services-basement/src/main/java/org/microg/safeparcel/ReflectedSafeParcelableCreatorAndWriter.java @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.safeparcel; + +import android.os.Parcel; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.lang.reflect.Array; + +public class ReflectedSafeParcelableCreatorAndWriter implements SafeParcelableCreatorAndWriter { + + private final SafeParcelReflectionUtil.ClassDescriptor descriptor; + + public ReflectedSafeParcelableCreatorAndWriter(Class tClass) { + this.descriptor = new SafeParcelReflectionUtil.ClassDescriptor<>(tClass); + } + + @Override + public T createFromParcel(Parcel parcel) { + return SafeParcelReflectionUtil.createObject(parcel, descriptor); + } + + @Override + public void writeToParcel(T object, Parcel parcel, int flags) { + SafeParcelReflectionUtil.writeObject(object, parcel, flags, descriptor); + } + + @SuppressWarnings("unchecked") + @Override + public T[] newArray(int i) { + return (T[]) Array.newInstance(descriptor.tClass, i); + } +} diff --git a/play-services-basement/src/main/java/org/microg/safeparcel/SafeParcelReflectionUtil.java b/play-services-basement/src/main/java/org/microg/safeparcel/SafeParcelReflectionUtil.java new file mode 100644 index 0000000000..20083b227d --- /dev/null +++ b/play-services-basement/src/main/java/org/microg/safeparcel/SafeParcelReflectionUtil.java @@ -0,0 +1,524 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.safeparcel; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.util.SparseArray; +import com.google.android.gms.common.internal.safeparcel.SafeParcelReader; +import com.google.android.gms.common.internal.safeparcel.SafeParcelWriter; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import org.microg.gms.common.Hide; + +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Hide +public final class SafeParcelReflectionUtil { + private static final String TAG = "SafeParcel"; + + private SafeParcelReflectionUtil() { + } + + @Deprecated + public static T createObject(Class tClass, Parcel in) { + ClassDescriptor descriptor = new ClassDescriptor<>(tClass); + return createObject(in, descriptor); + } + + public static T createObject(Parcel in, ClassDescriptor descriptor) { + try { + Constructor constructor = descriptor.constructor; + T t = constructor.newInstance(); + readObject(t, in, descriptor); + return t; + } catch (Exception e) { + throw new RuntimeException("Can't construct object", e); + } + } + + @Deprecated + public static void writeObject(AutoSafeParcelable object, Parcel parcel, int flags) { + if (object == null) + throw new NullPointerException(); + Class clazz = object.getClass(); + ClassDescriptor descriptor = new ClassDescriptor<>(clazz); + writeObject(object, parcel, flags, descriptor); + } + + public static void writeObject(T object, Parcel parcel, int flags, ClassDescriptor descriptor) { + int start = SafeParcelWriter.writeObjectHeader(parcel); + for (int i = 0; i < descriptor.fields.size(); i++) { + ClassDescriptor.FieldDescriptor fieldDescriptor = descriptor.fields.valueAt(i); + try { + writeField(object, parcel, flags, fieldDescriptor); + } catch (Exception e) { + Log.w(TAG, "Error writing field: " + e); + } + } + SafeParcelWriter.finishObjectHeader(parcel, start); + } + + @SuppressWarnings("unchecked") + @Deprecated + public static void readObject(T object, Parcel parcel) { + if (object == null) + throw new NullPointerException(); + Class clazz = (Class) object.getClass(); + ClassDescriptor descriptor = new ClassDescriptor<>(clazz); + readObject(object, parcel, descriptor); + } + + public static void readObject(T object, Parcel parcel, ClassDescriptor descriptor) { + if (object == null) + throw new NullPointerException(); + int end = SafeParcelReader.readObjectHeader(parcel); + while (parcel.dataPosition() < end) { + int header = SafeParcelReader.readHeader(parcel); + int fieldId = SafeParcelReader.getFieldId(header); + ClassDescriptor.FieldDescriptor fieldDescriptor = descriptor.fields.get(fieldId); + if (fieldDescriptor == null) { + Log.d(TAG, String.format("Unknown field id %d in %s, skipping.", fieldId, descriptor.tClass.getName())); + SafeParcelReader.skip(parcel, header); + } else { + try { + readField(object, parcel, header, fieldDescriptor); + } catch (Exception e) { + Log.w(TAG, String.format("Error reading field: %d in %s, skipping.", fieldId, descriptor.tClass.getName()), e); + SafeParcelReader.skip(parcel, header); + } + } + } + if (parcel.dataPosition() > end) { + throw new RuntimeException("Overread allowed size end=" + end); + } + } + + @SuppressWarnings("unchecked") + private static Parcelable.Creator getCreator(Field field) { + Class clazz = field.getType(); + if (clazz.isArray()) { + clazz = clazz.getComponentType(); + } + if (clazz != null && Parcelable.class.isAssignableFrom(clazz)) { + return getCreator((Class) clazz); + } + throw new RuntimeException(clazz + " is not an Parcelable"); + } + + @SuppressWarnings("unchecked") + public static Parcelable.Creator getCreator(Class clazz) { + try { + Field creatorField = clazz.getDeclaredField("CREATOR"); + creatorField.setAccessible(true); + return (Parcelable.Creator) creatorField.get(null); + } catch (NoSuchFieldException e) { + throw new RuntimeException(clazz + " is an Parcelable without CREATOR"); + } catch (IllegalAccessException e) { + throw new RuntimeException("CREATOR in " + clazz + " is not accessible"); + } + } + + @SuppressWarnings("deprecation") + private static Class getSubClass(Field field) { + SafeParceled safeParceled = field.getAnnotation(SafeParceled.class); + SafeParcelable.Field safeParcelableField = field.getAnnotation(SafeParcelable.Field.class); + if (safeParceled != null && safeParceled.subClass() != SafeParceled.class) { + return safeParceled.subClass(); + } else if (safeParceled != null && !"undefined".equals(safeParceled.subType())) { + try { + return Class.forName(safeParceled.subType()); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(e); + } + } else if (safeParcelableField != null && safeParcelableField.subClass() != SafeParcelable.class) { + return safeParcelableField.subClass(); + } else { + return null; + } + } + + @SuppressWarnings("deprecation") + private static Class getListItemClass(Field field) { + Class subClass = getSubClass(field); + if (subClass != null || field.isAnnotationPresent(SafeParceled.class)) return subClass; + Type type = field.getGenericType(); + if (type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) type; + if (pt.getActualTypeArguments().length >= 1) { + Type t = pt.getActualTypeArguments()[0]; + if (t instanceof Class) return (Class) t; + } + } + return null; + } + + private static ClassLoader getClassLoader(Class clazz) { + return clazz == null || clazz.getClassLoader() == null ? ClassLoader.getSystemClassLoader() : clazz.getClassLoader(); + } + + @SuppressWarnings("deprecation") + private static boolean isSafeParceledField(Field field) { + return field.isAnnotationPresent(SafeParceled.class) || field.isAnnotationPresent(SafeParcelable.Field.class); + } + + @SuppressWarnings("unchecked") + private static void writeField(AutoSafeParcelable object, Parcel parcel, int flags, ClassDescriptor.FieldDescriptor descriptor) + throws IllegalAccessException { + switch (descriptor.type) { + case Parcelable: + SafeParcelWriter.write(parcel, descriptor.id, (Parcelable) descriptor.field.get(object), flags, descriptor.mayNull); + break; + case Binder: + SafeParcelWriter.write(parcel, descriptor.id, (IBinder) descriptor.field.get(object), descriptor.mayNull); + break; + case Interface: + SafeParcelWriter.write(parcel, descriptor.id, ((IInterface) descriptor.field.get(object)).asBinder(), descriptor.mayNull); + break; + case StringList: + SafeParcelWriter.writeStringList(parcel, descriptor.id, ((List) descriptor.field.get(object)), descriptor.mayNull); + break; + case IntegerList: + SafeParcelWriter.writeIntegerList(parcel, descriptor.id, ((List) descriptor.field.get(object)), descriptor.mayNull); + break; + case BooleanList: + SafeParcelWriter.writeBooleanList(parcel, descriptor.id, ((List) descriptor.field.get(object)), descriptor.mayNull); + break; + case LongList: + SafeParcelWriter.writeLongList(parcel, descriptor.id, ((List) descriptor.field.get(object)), descriptor.mayNull); + break; + case FloatList: + SafeParcelWriter.writeFloatList(parcel, descriptor.id, ((List) descriptor.field.get(object)), descriptor.mayNull); + break; + case DoubleList: + SafeParcelWriter.writeDoubleList(parcel, descriptor.id, ((List) descriptor.field.get(object)), descriptor.mayNull); + break; + case List: { + Class clazz = descriptor.listItemClass; + if (clazz == null || !Parcelable.class.isAssignableFrom(clazz) || descriptor.useValueParcel) { + SafeParcelWriter.write(parcel, descriptor.id, (List) descriptor.field.get(object), descriptor.mayNull); + } else { + SafeParcelWriter.write(parcel, descriptor.id, (List) descriptor.field.get(object), flags, descriptor.mayNull); + } + break; + } + case Map: + SafeParcelWriter.write(parcel, descriptor.id, (Map) descriptor.field.get(object), descriptor.mayNull); + break; + case Bundle: + SafeParcelWriter.write(parcel, descriptor.id, (Bundle) descriptor.field.get(object), descriptor.mayNull); + break; + case ParcelableArray: + SafeParcelWriter.write(parcel, descriptor.id, (Parcelable[]) descriptor.field.get(object), flags, descriptor.mayNull); + break; + case StringArray: + SafeParcelWriter.write(parcel, descriptor.id, (String[]) descriptor.field.get(object), descriptor.mayNull); + break; + case ByteArray: + SafeParcelWriter.write(parcel, descriptor.id, (byte[]) descriptor.field.get(object), descriptor.mayNull); + break; + case ByteArrayArray: + SafeParcelWriter.write(parcel, descriptor.id, (byte[][]) descriptor.field.get(object), descriptor.mayNull); + break; + case FloatArray: + SafeParcelWriter.write(parcel, descriptor.id, (float[]) descriptor.field.get(object), descriptor.mayNull); + break; + case IntArray: + SafeParcelWriter.write(parcel, descriptor.id, (int[]) descriptor.field.get(object), descriptor.mayNull); + break; + case Integer: + SafeParcelWriter.write(parcel, descriptor.id, (Integer) descriptor.field.get(object)); + break; + case Long: + SafeParcelWriter.write(parcel, descriptor.id, (Long) descriptor.field.get(object)); + break; + case Boolean: + SafeParcelWriter.write(parcel, descriptor.id, (Boolean) descriptor.field.get(object)); + break; + case Float: + SafeParcelWriter.write(parcel, descriptor.id, (Float) descriptor.field.get(object)); + break; + case Double: + SafeParcelWriter.write(parcel, descriptor.id, (Double) descriptor.field.get(object)); + break; + case String: + SafeParcelWriter.write(parcel, descriptor.id, (String) descriptor.field.get(object), descriptor.mayNull); + break; + case Byte: + SafeParcelWriter.write(parcel, descriptor.id, (Byte) descriptor.field.get(object)); + break; + } + } + + private static void readField(AutoSafeParcelable object, Parcel parcel, int header, ClassDescriptor.FieldDescriptor descriptor) + throws IllegalAccessException { + switch (descriptor.type) { + case Parcelable: + descriptor.field.set(object, SafeParcelReader.readParcelable(parcel, header, descriptor.creator)); + break; + case Binder: + descriptor.field.set(object, SafeParcelReader.readBinder(parcel, header)); + break; + case Interface: { + boolean hasStub = false; + for (Class aClass : descriptor.field.getType().getDeclaredClasses()) { + try { + descriptor.field.set(object, aClass.getDeclaredMethod("asInterface", IBinder.class) + .invoke(null, SafeParcelReader.readBinder(parcel, header))); + hasStub = true; + break; + } catch (Exception ignored) { + } + } + if (!hasStub) throw new RuntimeException("Field has broken interface: " + descriptor.field); + break; + } + case StringList: + descriptor.field.set(object, SafeParcelReader.readStringList(parcel, header)); + break; + case IntegerList: + descriptor.field.set(object, SafeParcelReader.readIntegerList(parcel, header)); + break; + case BooleanList: + descriptor.field.set(object, SafeParcelReader.readBooleanList(parcel, header)); + break; + case LongList: + descriptor.field.set(object, SafeParcelReader.readLongList(parcel, header)); + break; + case FloatList: + descriptor.field.set(object, SafeParcelReader.readFloatList(parcel, header)); + break; + case DoubleList: + descriptor.field.set(object, SafeParcelReader.readDoubleList(parcel, header)); + break; + case List: { + Class clazz = descriptor.listItemClass; + Object val; + if (clazz == null || !Parcelable.class.isAssignableFrom(clazz) || descriptor.useValueParcel) { + val = SafeParcelReader.readList(parcel, header, getClassLoader(clazz)); + } else { + val = SafeParcelReader.readParcelableList(parcel, header, descriptor.creator); + } + descriptor.field.set(object, val); + break; + } + case Map: { + Class clazz = descriptor.subClass; + Object val = SafeParcelReader.readMap(parcel, header, getClassLoader(clazz)); + descriptor.field.set(object, val); + break; + } + case Bundle: { + Class clazz = descriptor.subClass; + Object val; + if (clazz == null || !Parcelable.class.isAssignableFrom(clazz) || descriptor.useValueParcel /* should not happen on Bundles */) { + val = SafeParcelReader.readBundle(parcel, header, getClassLoader(descriptor.field.getDeclaringClass())); + } else { + val = SafeParcelReader.readBundle(parcel, header, getClassLoader(clazz)); + } + descriptor.field.set(object, val); + break; + } + case ParcelableArray: + descriptor.field.set(object, SafeParcelReader.readParcelableArray(parcel, header, descriptor.creator)); + break; + case StringArray: + descriptor.field.set(object, SafeParcelReader.readStringArray(parcel, header)); + break; + case ByteArray: + descriptor.field.set(object, SafeParcelReader.readByteArray(parcel, header)); + break; + case ByteArrayArray: + descriptor.field.set(object, SafeParcelReader.readByteArrayArray(parcel, header)); + break; + case FloatArray: + descriptor.field.set(object, SafeParcelReader.readFloatArray(parcel, header)); + break; + case IntArray: + descriptor.field.set(object, SafeParcelReader.readIntArray(parcel, header)); + break; + case Integer: { + int i = SafeParcelReader.readInt(parcel, header); + if (descriptor.versionCode != -1 && i > descriptor.versionCode) { + Log.d(TAG, String.format("Version code of %s (%d) is older than object read (%d).", descriptor.field.getDeclaringClass().getName(), descriptor.versionCode, i)); + } + descriptor.field.set(object, i); + break; + } + case Long: { + long l = SafeParcelReader.readLong(parcel, header); + if (descriptor.versionCode != -1 && l > descriptor.versionCode) { + Log.d(TAG, String.format("Version code of %s (%d) is older than object read (%d).", descriptor.field.getDeclaringClass().getName(), descriptor.versionCode, l)); + } + descriptor.field.set(object, l); + break; + } + case Boolean: + descriptor.field.set(object, SafeParcelReader.readBool(parcel, header)); + break; + case Float: + descriptor.field.set(object, SafeParcelReader.readFloat(parcel, header)); + break; + case Double: + descriptor.field.set(object, SafeParcelReader.readDouble(parcel, header)); + break; + case String: + descriptor.field.set(object, SafeParcelReader.readString(parcel, header)); + break; + case Byte: + descriptor.field.set(object, SafeParcelReader.readByte(parcel, header)); + break; + default: + throw new IllegalStateException("Unexpected value: " + descriptor.type); + } + } + + private enum SafeParcelType { + Parcelable, Binder, Interface, Bundle, + StringList, IntegerList, BooleanList, LongList, FloatList, DoubleList, List, Map, + ParcelableArray, StringArray, ByteArray, ByteArrayArray, FloatArray, IntArray, + Integer, Long, Boolean, Float, Double, String, Byte; + } + + public static class ClassDescriptor { + Class tClass; + Constructor constructor; + SparseArray fields = new SparseArray<>(); + + public ClassDescriptor(Class tClass) { + this.tClass = tClass; + try { + constructor = tClass.getDeclaredConstructor(); + constructor.setAccessible(true); + } catch (Exception e) { + Log.w(TAG, tClass + " has no default constructor"); + } + Class clazz = tClass; + while (clazz != null) { + for (Field field : clazz.getDeclaredFields()) { + if (isSafeParceledField(field)) { + FieldDescriptor fieldDescriptor = new FieldDescriptor(field); + fields.set(fieldDescriptor.id, fieldDescriptor); + } + } + clazz = clazz.getSuperclass(); + } + } + + public static class FieldDescriptor { + Field field; + int id; + boolean mayNull; + SafeParcelable.Field annotation; + SafeParceled legacyAnnotation; + SafeParcelType type; + Parcelable.Creator creator; + long versionCode = -1; + Class listItemClass; + boolean useValueParcel; + Class subClass; + + public FieldDescriptor(Field field) { + this.field = field; + field.setAccessible(true); + try { + Field accessFlagsField = Field.class.getDeclaredField("accessFlags"); + accessFlagsField.setAccessible(true); + accessFlagsField.setInt(field, accessFlagsField.getInt(field) & ~Modifier.FINAL); + } catch (Exception e) { + // Ignored + } + this.annotation = field.getAnnotation(SafeParcelable.Field.class); + this.legacyAnnotation = field.getAnnotation(SafeParceled.class); + if (annotation != null) { + this.id = annotation.value(); + this.mayNull = annotation.mayNull(); + this.useValueParcel = annotation.useValueParcel(); + this.versionCode = annotation.versionCode(); + } else if (legacyAnnotation != null) { + this.id = legacyAnnotation.value(); + this.mayNull = legacyAnnotation.mayNull(); + this.useValueParcel = legacyAnnotation.useClassLoader(); + } else { + throw new IllegalArgumentException(); + } + this.type = getType(); + switch (type) { + case Parcelable: + case ParcelableArray: + creator = getCreator(field); + break; + case List: + if (listItemClass != null && Parcelable.class.isAssignableFrom(listItemClass)) { + if (!this.useValueParcel) { + creator = getCreator((Class) listItemClass); + } + } + break; + case Map: + case Bundle: + subClass = getSubClass(field); + break; + } + } + + private SafeParcelType getType() { + Class clazz = field.getType(); + Class component = clazz.getComponentType(); + if (clazz.isArray() && component != null) { + if (Parcelable.class.isAssignableFrom(component)) return SafeParcelType.ParcelableArray; + if (String.class.isAssignableFrom(component)) return SafeParcelType.StringArray; + if (byte.class.isAssignableFrom(component)) return SafeParcelType.ByteArray; + if (byte[].class.isAssignableFrom(component)) return SafeParcelType.ByteArrayArray; + if (float.class.isAssignableFrom(component)) return SafeParcelType.FloatArray; + if (int.class.isAssignableFrom(component)) return SafeParcelType.IntArray; + } + if (Bundle.class.isAssignableFrom(clazz)) + return SafeParcelType.Bundle; + if (Parcelable.class.isAssignableFrom(clazz)) + return SafeParcelType.Parcelable; + if (IBinder.class.isAssignableFrom(clazz)) + return SafeParcelType.Binder; + if (IInterface.class.isAssignableFrom(clazz)) + return SafeParcelType.Interface; + if (clazz == List.class || clazz == ArrayList.class) { + listItemClass = getListItemClass(field); + if (listItemClass == String.class && useValueParcel) return SafeParcelType.StringList; + if (listItemClass == Integer.class && annotation.useDirectList()) return SafeParcelType.IntegerList; + if (listItemClass == Boolean.class && annotation.useDirectList()) return SafeParcelType.BooleanList; + if (listItemClass == Long.class && annotation.useDirectList()) return SafeParcelType.LongList; + if (listItemClass == Float.class && annotation.useDirectList()) return SafeParcelType.FloatList; + if (listItemClass == Double.class && annotation.useDirectList()) return SafeParcelType.DoubleList; + return SafeParcelType.List; + } + if (clazz == Map.class || clazz == HashMap.class) + return SafeParcelType.Map; + if (clazz == int.class || clazz == Integer.class) + return SafeParcelType.Integer; + if (clazz == boolean.class || clazz == Boolean.class) + return SafeParcelType.Boolean; + if (clazz == long.class || clazz == Long.class) + return SafeParcelType.Long; + if (clazz == float.class || clazz == Float.class) + return SafeParcelType.Float; + if (clazz == double.class || clazz == Double.class) + return SafeParcelType.Double; + if (clazz == byte.class || clazz == Byte.class) + return SafeParcelType.Byte; + if (clazz == java.lang.String.class) + return SafeParcelType.String; + throw new RuntimeException("Type is not yet usable with SafeParcelReflectionUtil: " + clazz); + } + } + } +} diff --git a/play-services-basement/src/main/java/org/microg/safeparcel/SafeParceled.java b/play-services-basement/src/main/java/org/microg/safeparcel/SafeParceled.java new file mode 100644 index 0000000000..91f0916ee1 --- /dev/null +++ b/play-services-basement/src/main/java/org/microg/safeparcel/SafeParceled.java @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2015, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.safeparcel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Deprecated +public @interface SafeParceled { + int value(); + + boolean mayNull() default false; + + @Deprecated String subType() default "undefined"; + + Class subClass() default SafeParceled.class; + + boolean useClassLoader() default false; +} \ No newline at end of file diff --git a/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java index a86104443a..1ef5a45615 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java @@ -41,6 +41,7 @@ import com.google.android.gms.R; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import com.google.android.gms.location.places.internal.PlaceImpl; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; @@ -48,7 +49,6 @@ import org.microg.gms.location.LocationConstants; //import org.microg.gms.maps.vtm.BackendMapView; //import org.microg.gms.maps.vtm.GmsMapsTypeHelper; -import org.microg.safeparcel.SafeParcelUtil; //import org.oscim.core.MapPosition; //import org.oscim.event.Event; //import org.oscim.map.Map; @@ -117,9 +117,9 @@ protected void onCreate(Bundle savedInstanceState) { findViewById(R.id.place_picker_select).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - resultIntent.putExtra(LocationConstants.EXTRA_STATUS, SafeParcelUtil.asByteArray(new Status(CommonStatusCodes.SUCCESS))); - resultIntent.putExtra(LocationConstants.EXTRA_PLACE, SafeParcelUtil.asByteArray(place)); - resultIntent.putExtra(LocationConstants.EXTRA_FINAL_BOUNDS, SafeParcelUtil.asByteArray(place.viewport)); + resultIntent.putExtra(LocationConstants.EXTRA_STATUS, SafeParcelableSerializer.serializeToBytes(new Status(CommonStatusCodes.SUCCESS))); + resultIntent.putExtra(LocationConstants.EXTRA_PLACE, SafeParcelableSerializer.serializeToBytes(place)); + resultIntent.putExtra(LocationConstants.EXTRA_FINAL_BOUNDS, SafeParcelableSerializer.serializeToBytes(place.viewport)); setResult(RESULT_OK, resultIntent); finish(); } diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java index 84bf5770ee..c01bec0f4d 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java @@ -8,9 +8,9 @@ package com.google.android.gms.fido.fido2.api.common; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; @@ -31,7 +31,7 @@ public UvmEntries getUvmEntries() { * Use {@link #deserializeFromBytes(byte[])} to deserialize. */ public byte[] serializeToBytes() { - return SafeParcelUtil.asByteArray(this); + return SafeParcelableSerializer.serializeToBytes(this); } /** @@ -40,7 +40,7 @@ public byte[] serializeToBytes() { * @return The deserialized {@link AuthenticationExtensionsClientOutputs} */ public static AuthenticationExtensionsClientOutputs deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } @Override diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java index 1dc01d812e..2bbe0c0ee6 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java @@ -8,9 +8,9 @@ package com.google.android.gms.fido.fido2.api.common; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.gms.utils.ToStringHelper; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; @@ -68,7 +68,7 @@ public byte[] getUserHandle() { @Override public byte[] serializeToBytes() { - return SafeParcelUtil.asByteArray(this); + return SafeParcelableSerializer.serializeToBytes(this); } @Override @@ -102,7 +102,7 @@ public String toString() { } public static AuthenticatorAssertionResponse deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } public static final Creator CREATOR = new AutoCreator<>(AuthenticatorAssertionResponse.class); diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java index 174c5aa071..33537104bb 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java @@ -8,9 +8,9 @@ package com.google.android.gms.fido.fido2.api.common; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.gms.utils.ToStringHelper; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; @@ -54,7 +54,7 @@ public byte[] getKeyHandle() { @Override public byte[] serializeToBytes() { - return SafeParcelUtil.asByteArray(this); + return SafeParcelableSerializer.serializeToBytes(this); } @Override @@ -84,7 +84,7 @@ public String toString() { } public static AuthenticatorAttestationResponse deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } public static final Creator CREATOR = new AutoCreator<>(AuthenticatorAttestationResponse.class); diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java index 1853902acf..1c20bfc5c3 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java @@ -8,9 +8,9 @@ package com.google.android.gms.fido.fido2.api.common; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.gms.utils.ToStringHelper; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; @@ -52,7 +52,7 @@ public String getErrorMessage() { @Override public byte[] serializeToBytes() { - return SafeParcelUtil.asByteArray(this); + return SafeParcelableSerializer.serializeToBytes(this); } @Override @@ -80,7 +80,7 @@ public String toString() { } public static AuthenticatorErrorResponse deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } public static final Creator CREATOR = new AutoCreator<>(AuthenticatorErrorResponse.class); diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java index dd79edf0ec..a24b170456 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java @@ -7,9 +7,9 @@ import android.net.Uri; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.gms.utils.ToStringHelper; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; @@ -149,7 +149,7 @@ public BrowserPublicKeyCredentialCreationOptions build() { } public static BrowserPublicKeyCredentialCreationOptions deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } public static final Creator CREATOR = new AutoCreator<>(BrowserPublicKeyCredentialCreationOptions.class); diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java index d0e2b79ced..be63a72ec6 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java @@ -7,9 +7,9 @@ import android.net.Uri; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.gms.utils.ToStringHelper; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; @@ -149,7 +149,7 @@ public BrowserPublicKeyCredentialRequestOptions build() { } public static BrowserPublicKeyCredentialRequestOptions deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } public static final Creator CREATOR = new AutoCreator<>(BrowserPublicKeyCredentialRequestOptions.class); diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java index ebc5f819a1..f9d657b37c 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java @@ -8,9 +8,9 @@ package com.google.android.gms.fido.fido2.api.common; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; @@ -160,7 +160,7 @@ public int hashCode() { * @return the serialized byte array. */ public byte[] serializeToBytes() { - return SafeParcelUtil.asByteArray(this); + return SafeParcelableSerializer.serializeToBytes(this); } /** @@ -170,7 +170,7 @@ public byte[] serializeToBytes() { * @return The deserialized {@link PublicKeyCredential}. */ public static PublicKeyCredential deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } @PublicApi(exclude = true) diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java index bc44f1d022..0b8096a6db 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java @@ -5,9 +5,9 @@ package com.google.android.gms.fido.fido2.api.common; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.gms.utils.ToStringHelper; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; import java.util.List; @@ -276,7 +276,7 @@ public PublicKeyCredentialCreationOptions build() { * @return The deserialized {@link PublicKeyCredentialCreationOptions}. */ public static PublicKeyCredentialCreationOptions deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } @PublicApi(exclude = true) diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java index 5f9102b650..267043b007 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java @@ -8,9 +8,9 @@ package com.google.android.gms.fido.fido2.api.common; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.gms.utils.ToStringHelper; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Arrays; import java.util.List; @@ -217,7 +217,7 @@ public PublicKeyCredentialRequestOptions build() { * @return The deserialized {@link PublicKeyCredentialRequestOptions}. */ public static PublicKeyCredentialRequestOptions deserializeFromBytes(byte[] serializedBytes) { - return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(serializedBytes, CREATOR); } @PublicApi(exclude = true) diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java index f2cc361bf3..94bd919043 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java @@ -8,9 +8,9 @@ package com.google.android.gms.fido.fido2.api.common; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParcelUtil; /** * An abstract class representing FIDO2 request options. @@ -27,6 +27,6 @@ public abstract class RequestOptions extends AutoSafeParcelable { * Serializes the {@link RequestOptions} to bytes. Use deserializeFromBytes(byte[]) to deserialize. */ public byte[] serializeToBytes() { - return SafeParcelUtil.asByteArray(this); + return SafeParcelableSerializer.serializeToBytes(this); } } diff --git a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LastLocationCapsule.kt b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LastLocationCapsule.kt index 69ceeb7295..642c3d8783 100644 --- a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LastLocationCapsule.kt +++ b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LastLocationCapsule.kt @@ -13,14 +13,14 @@ import android.os.SystemClock import android.util.Log import androidx.core.content.getSystemService import androidx.core.location.LocationCompat +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer import com.google.android.gms.location.Granularity import com.google.android.gms.location.Granularity.GRANULARITY_COARSE import com.google.android.gms.location.Granularity.GRANULARITY_FINE import com.google.android.gms.location.LocationAvailability import org.microg.gms.location.elapsedMillis import org.microg.safeparcel.AutoSafeParcelable -import org.microg.safeparcel.SafeParcelUtil -import org.microg.safeparcel.SafeParcelable.Field import java.io.File import java.lang.Long.max import java.util.concurrent.TimeUnit @@ -101,7 +101,7 @@ class LastLocationCapsule(private val context: Context) { } try { if (file.exists()) { - val capsule = SafeParcelUtil.fromByteArray(file.readBytes(), LastLocationCapsuleParcelable.CREATOR) + val capsule = SafeParcelableSerializer.deserializeFromBytes(file.readBytes(), LastLocationCapsuleParcelable.CREATOR) lastFineLocation = capsule.lastFineLocation?.adjustRealtime() lastCoarseLocation = capsule.lastCoarseLocation?.adjustRealtime() lastFineLocationTimeCoarsed = capsule.lastFineLocationTimeCoarsed?.adjustRealtime() @@ -123,7 +123,7 @@ class LastLocationCapsule(private val context: Context) { fun stop() { try { if (file.exists()) file.delete() - file.writeBytes(SafeParcelUtil.asByteArray(LastLocationCapsuleParcelable(lastFineLocation, lastCoarseLocation, lastFineLocationTimeCoarsed, lastCoarseLocationTimeCoarsed))) + file.writeBytes(SafeParcelableSerializer.serializeToBytes(LastLocationCapsuleParcelable(lastFineLocation, lastCoarseLocation, lastFineLocationTimeCoarsed, lastCoarseLocationTimeCoarsed))) } catch (e: Exception) { Log.w(TAG, e) // Ignore diff --git a/play-services-location/core/src/main/kotlin/org/microg/gms/location/ui/LocationAppFragment.kt b/play-services-location/core/src/main/kotlin/org/microg/gms/location/ui/LocationAppFragment.kt index f9390f8033..187647f530 100644 --- a/play-services-location/core/src/main/kotlin/org/microg/gms/location/ui/LocationAppFragment.kt +++ b/play-services-location/core/src/main/kotlin/org/microg/gms/location/ui/LocationAppFragment.kt @@ -55,7 +55,7 @@ class LocationAppFragment : PreferenceFragmentCompat() { lastLocationMap = preferenceScreen.findPreference("pref_location_app_last_location_map") ?: lastLocationMap forceCoarse = preferenceScreen.findPreference("pref_location_app_force_coarse") ?: forceCoarse forceCoarse.setOnPreferenceChangeListener { _, newValue -> - packageName?.let { database.setForceCoarse(it, newValue as Boolean); true} == true + packageName?.let { database.setForceCoarse(it, newValue as Boolean); true } == true } } @@ -86,7 +86,12 @@ class LocationAppFragment : PreferenceFragmentCompat() { if (location != null) { lastLocationCategory.isVisible = true lastLocation.title = DateUtils.getRelativeTimeSpanString(location.time) - lastLocation.intent = Intent(Intent.ACTION_VIEW, Uri.parse("geo:${location.latitude},${location.longitude}")) + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("geo:${location.latitude},${location.longitude}")) + if (context.packageManager.queryIntentActivities(intent, 0).isNotEmpty()) { + lastLocation.intent = intent + } else { + lastLocation.isSelectable = false + } lastLocationMap.location = location val address = try { if (SDK_INT > 33) { @@ -117,7 +122,7 @@ class LocationAppFragment : PreferenceFragmentCompat() { } lastLocation.summary = addressLine.toString() } else { - lastLocation.summary = "${location.latitude.toStringWithDigits(6)}, ${location.longitude.toStringWithDigits(6)}" + lastLocation.summary = "${location.latitude.toStringWithDigits(6)}, ${location.longitude.toStringWithDigits(6)}" } } else { lastLocationCategory.isVisible = false diff --git a/play-services-location/src/main/java/com/google/android/gms/location/ActivityRecognitionResult.java b/play-services-location/src/main/java/com/google/android/gms/location/ActivityRecognitionResult.java index cf3b86280b..fe853c2709 100644 --- a/play-services-location/src/main/java/com/google/android/gms/location/ActivityRecognitionResult.java +++ b/play-services-location/src/main/java/com/google/android/gms/location/ActivityRecognitionResult.java @@ -13,9 +13,9 @@ import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Collections; import java.util.List; @@ -100,7 +100,7 @@ public static ActivityRecognitionResult extractResult(Intent intent) { if (res instanceof ActivityRecognitionResult) return (ActivityRecognitionResult) res; if (res instanceof byte[]) - return SafeParcelUtil.fromByteArray((byte[]) res, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes((byte[]) res, CREATOR); } return null; } diff --git a/play-services-location/src/main/java/com/google/android/gms/location/ActivityTransitionRequest.java b/play-services-location/src/main/java/com/google/android/gms/location/ActivityTransitionRequest.java index 33947e785a..3c858a145f 100644 --- a/play-services-location/src/main/java/com/google/android/gms/location/ActivityTransitionRequest.java +++ b/play-services-location/src/main/java/com/google/android/gms/location/ActivityTransitionRequest.java @@ -11,9 +11,9 @@ import android.content.Intent; import androidx.annotation.NonNull; import com.google.android.gms.common.internal.ClientIdentity; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParcelUtil; import java.util.*; @@ -72,7 +72,7 @@ public ActivityTransitionRequest(List transitions) { * @param intent the intent to serailize this object to */ public void serializeToIntentExtra(Intent intent) { - intent.putExtra(EXTRA, SafeParcelUtil.asByteArray(this)); + intent.putExtra(EXTRA, SafeParcelableSerializer.serializeToBytes(this)); } @Override diff --git a/play-services-location/src/main/java/com/google/android/gms/location/ActivityTransitionResult.java b/play-services-location/src/main/java/com/google/android/gms/location/ActivityTransitionResult.java index 3abe47294d..cb7a6902d1 100644 --- a/play-services-location/src/main/java/com/google/android/gms/location/ActivityTransitionResult.java +++ b/play-services-location/src/main/java/com/google/android/gms/location/ActivityTransitionResult.java @@ -11,9 +11,9 @@ import android.content.Intent; import android.os.Bundle; import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParcelUtil; import java.util.Collections; import java.util.List; @@ -62,7 +62,7 @@ public List getTransitionEvents() { */ public static ActivityTransitionResult extractResult(Intent intent) { if (!hasResult(intent)) return null; - return SafeParcelUtil.fromByteArray(intent.getByteArrayExtra(EXTRA), CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(intent.getByteArrayExtra(EXTRA), CREATOR); } /** diff --git a/play-services-location/src/main/java/com/google/android/gms/location/GeofencingEvent.java b/play-services-location/src/main/java/com/google/android/gms/location/GeofencingEvent.java index 16706fa0cd..85a5b2c538 100644 --- a/play-services-location/src/main/java/com/google/android/gms/location/GeofencingEvent.java +++ b/play-services-location/src/main/java/com/google/android/gms/location/GeofencingEvent.java @@ -12,11 +12,11 @@ import android.content.Intent; import android.location.Location; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import com.google.android.gms.location.internal.ParcelableGeofence; import org.microg.gms.common.Hide; import org.microg.gms.common.PublicApi; -import org.microg.safeparcel.SafeParcelUtil; import java.util.ArrayList; import java.util.List; @@ -61,7 +61,7 @@ public static GeofencingEvent fromIntent(Intent intent) { if (parceledGeofences != null) { event.triggeringGeofences = new ArrayList(); for (byte[] parceledGeofence : parceledGeofences) { - event.triggeringGeofences.add(SafeParcelUtil.fromByteArray(parceledGeofence, ParcelableGeofence.CREATOR)); + event.triggeringGeofences.add(SafeParcelableSerializer.deserializeFromBytes(parceledGeofence, ParcelableGeofence.CREATOR)); } } event.triggeringLocation = intent.getParcelableExtra(EXTRA_TRIGGERING_LOCATION); diff --git a/play-services-location/src/main/java/com/google/android/gms/location/LocationSettingsStates.java b/play-services-location/src/main/java/com/google/android/gms/location/LocationSettingsStates.java index bc0dffd87f..b4c14fd439 100644 --- a/play-services-location/src/main/java/com/google/android/gms/location/LocationSettingsStates.java +++ b/play-services-location/src/main/java/com/google/android/gms/location/LocationSettingsStates.java @@ -10,10 +10,9 @@ import android.app.Activity; import android.content.Intent; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParcelUtil; -import org.microg.safeparcel.SafeParceled; /** * Stores the current states of all location-related settings. @@ -118,7 +117,7 @@ public LocationSettingsStates(boolean gpsUsable, boolean networkLocationUsable, public static LocationSettingsStates fromIntent(Intent intent) { byte[] bytes = intent.getByteArrayExtra(EXTRA_NAME); if (bytes == null) return null; - return SafeParcelUtil.fromByteArray(bytes, CREATOR); + return SafeParcelableSerializer.deserializeFromBytes(bytes, CREATOR); } public static final Creator CREATOR = new AutoCreator(LocationSettingsStates.class); diff --git a/play-services-maps/build.gradle b/play-services-maps/build.gradle index a59d8287aa..68b60de42c 100644 --- a/play-services-maps/build.gradle +++ b/play-services-maps/build.gradle @@ -32,4 +32,6 @@ dependencies { api "androidx.fragment:fragment:1.0.0" api project(":play-services-base") api project(":play-services-basement") + + annotationProcessor project(":safe-parcel-processor") } diff --git a/play-services-maps/src/main/java/com/google/android/gms/maps/model/LatLng.java b/play-services-maps/src/main/java/com/google/android/gms/maps/model/LatLng.java index 545d8b4687..f8233075a6 100644 --- a/play-services-maps/src/main/java/com/google/android/gms/maps/model/LatLng.java +++ b/play-services-maps/src/main/java/com/google/android/gms/maps/model/LatLng.java @@ -16,26 +16,29 @@ package com.google.android.gms.maps.model; +import android.os.Parcel; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import org.microg.gms.common.PublicApi; -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; /** * An immutable class representing a pair of latitude and longitude coordinates, stored as degrees. */ @PublicApi -public final class LatLng extends AutoSafeParcelable { - @SafeParceled(1) - private int versionCode = 1; +@SafeParcelable.Class +public final class LatLng extends AbstractSafeParcelable { + @Field(1) + int versionCode = 1; /** * Latitude, in degrees. This value is in the range [-90, 90]. */ - @SafeParceled(2) + @Field(2) public final double latitude; /** * Longitude, in degrees. This value is in the range [-180, 180). */ - @SafeParceled(3) + @Field(3) public final double longitude; /** @@ -46,6 +49,13 @@ private LatLng() { latitude = longitude = 0; } + @Constructor + LatLng(@Param(1) int versionCode, @Param(2) double latitude, @Param(3) double longitude) { + this.versionCode = versionCode; + this.latitude = latitude; + this.longitude = longitude; + } + /** * Constructs a LatLng with the given latitude and longitude, measured in degrees. * @@ -99,6 +109,11 @@ public final int hashCode() { public String toString() { return "lat/lng: (" + latitude + "," + longitude + ")"; } - - public static Creator CREATOR = new AutoCreator(LatLng.class); + + @Override + public void writeToParcel(Parcel out, int flags) { + CREATOR.writeToParcel(this, out, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = findCreator(LatLng.class); } diff --git a/safe-parcel-processor/build.gradle b/safe-parcel-processor/build.gradle new file mode 100644 index 0000000000..770686d110 --- /dev/null +++ b/safe-parcel-processor/build.gradle @@ -0,0 +1 @@ +apply plugin: 'kotlin' \ No newline at end of file diff --git a/safe-parcel-processor/src/main/AndroidManifest.xml b/safe-parcel-processor/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..429ff28064 --- /dev/null +++ b/safe-parcel-processor/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/safe-parcel-processor/src/main/kotlin/org/microg/safeparcel/SafeParcelProcessor.kt b/safe-parcel-processor/src/main/kotlin/org/microg/safeparcel/SafeParcelProcessor.kt new file mode 100644 index 0000000000..7393e10489 --- /dev/null +++ b/safe-parcel-processor/src/main/kotlin/org/microg/safeparcel/SafeParcelProcessor.kt @@ -0,0 +1,278 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.safeparcel + +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.Messager +import javax.annotation.processing.RoundEnvironment +import javax.annotation.processing.SupportedAnnotationTypes +import javax.annotation.processing.SupportedSourceVersion +import javax.lang.model.SourceVersion +import javax.lang.model.element.Element +import javax.lang.model.element.ElementKind +import javax.lang.model.element.ExecutableElement +import javax.lang.model.element.Modifier +import javax.lang.model.element.TypeElement +import javax.lang.model.element.VariableElement +import javax.tools.Diagnostic + +const val SafeParcelable = "com.google.android.gms.common.internal.safeparcel.SafeParcelable" +const val SafeParcelReader = "com.google.android.gms.common.internal.safeparcel.SafeParcelReader" +const val SafeParcelWriter = "com.google.android.gms.common.internal.safeparcel.SafeParcelWriter" +const val SafeParcelableCreatorAndWriter = "com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter" + +const val Field = "java.lang.reflect.Field" + +const val Log = "android.util.Log" +const val Parcel = "android.os.Parcel" + +@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedAnnotationTypes("$SafeParcelable.Class") +class SafeParcelProcessor : AbstractProcessor() { + override fun process(set: Set, roundEnvironment: RoundEnvironment): Boolean { + val safeParcelableClassTypeElement = set.firstOrNull() ?: return false + classes@ for (classElement in roundEnvironment.getElementsAnnotatedWith(safeParcelableClassTypeElement)) { + val clazz = ClassInfo(classElement) + if (clazz.check(processingEnv.messager)) { + processingEnv.filer.createSourceFile(clazz.fullCreatorName, clazz.classElement).openWriter().use { it.write(clazz.generateCreator()) } + } + } + return false + } +} + +class ClassInfo(val classElement: Element) { + val name = classElement.simpleName.toString() + val fullName = classElement.toString() + val packageName = fullName.split(".").dropLast(1).joinToString(".") + + val creatorName = "$name\$000Creator" + val fullCreatorName = "$packageName.$creatorName" + + val fields = classElement.enclosedElements + .filter { it.kind == ElementKind.FIELD && !it.modifiers.contains(Modifier.STATIC) } + .filterIsInstance() + .filter { it.annotationMirrors.any { it.annotationType.toString() == "$SafeParcelable.Field" } } + .map { FieldInfo(this, it) } + val constructor = classElement.enclosedElements + .filter { it.kind == ElementKind.CONSTRUCTOR } + .filterIsInstance() + .filter { it.annotationMirrors.any { it.annotationType.toString() == "$SafeParcelable.Constructor" } || it.parameters.isEmpty() } + .let { if (it.size == 2) it.first { it.parameters.isNotEmpty() } else it.firstOrNull() } + ?.let { ConstructorInfo(this, it) } + + fun check(messager: Messager): Boolean { + fun note(message: String) = messager.printMessage(Diagnostic.Kind.NOTE, message) + fun error(message: String) = messager.printMessage(Diagnostic.Kind.ERROR, message) + if (constructor == null) { + error("No suitable constructor found for $fullName") + return false + } + if (constructor.parameters.any { it.annotationMirrors.none { it.annotationType.toString() == "$SafeParcelable.Param" } }) { + error("Tagged constructor for $fullName has parameters without @Param.") + return false + } + if (constructor.fieldIds.any { id -> fields.none { it.id == id } }) { + error("Constructor for $fullName has parameters with @Param value without matching @Field.") + return false + } + if (constructor.isPrivate) { + note("Using reflection to construct $fullName from parcel. Consider providing a suitable package-visible constructor for improved performance.") + } + for (field in fields) { + if (field.type !in listOf("int", "long", "float", "double")) { + error("Field ${field.name} in $fullName has unsupported type.") + return false + } + if (field.isPrivate) { + note("Using reflection when accessing ${field.name} in $fullName. Consider adding it to the @Constructor and making the field package-visible for improved performance.") + } + } + return true + } + + fun generateCreator(): String { + if (constructor == null) throw IllegalStateException("Can't create Creator for class without constructor") + fun List.linesToString(prefix: String = "") = joinToString("\n $prefix") + val variableDeclarations = fields.map { it.variableDeclaration }.linesToString() + val setVariablesDefault = fields.map { it.setVariableDefault }.linesToString() + val readVariablesFromParcel = fields.map { it.readVariableFromParcelCase }.linesToString(" ") + val writeVariableToParcel = fields.map { it.writeVariableToParcel }.linesToString() + val setFieldsFromVariables = fields.filter { it.id !in constructor.fieldIds }.flatMap { it.setFieldFromVariable }.linesToString() + val invokeConstructor = constructor.invocation.linesToString() + val setVariablesFromFields = fields.flatMap { it.setVariableFromField }.linesToString() + val file = """ + package $packageName; + + //@javax.annotation.processing.Generated // Not supported by Android + @androidx.annotation.Keep + public class $creatorName implements $SafeParcelableCreatorAndWriter<$fullName> { + @Override + public $fullName createFromParcel($Parcel parcel) { + int end = $SafeParcelReader.readObjectHeader(parcel); + $fullName object; + try { + $variableDeclarations + $setVariablesDefault + while (parcel.dataPosition() < end) { + int header = $SafeParcelReader.readHeader(parcel); + int fieldId = $SafeParcelReader.getFieldId(header); + switch (fieldId) { + $readVariablesFromParcel + default: + $Log.d("SafeParcel", String.format("Unknown field id %d in %s, skipping.", fieldId, "$fullName")); + $SafeParcelReader.skip(parcel, header); + } + } + $invokeConstructor + $setFieldsFromVariables + } catch (Exception e) { + throw new RuntimeException(String.format("Error reading %s", "$fullName"), e); + } + if (parcel.dataPosition() > end) { + throw new RuntimeException(String.format("Overread allowed size end=%d", end)); + } + return object; + } + + @Override + public void writeToParcel($fullName object, $Parcel parcel, int flags) { + int start = $SafeParcelWriter.writeObjectHeader(parcel); + try { + $variableDeclarations + $setVariablesFromFields + $writeVariableToParcel + } catch (Exception e) { + throw new RuntimeException(String.format("Error writing %s", "$fullName"), e); + } + $SafeParcelWriter.finishObjectHeader(parcel, start); + } + + @Override + public $fullName[] newArray(int size) { + return new $fullName[size]; + } + } + """.trimIndent() + return file + } +} + +class ConstructorInfo(val clazz: ClassInfo, val constructorElement: ExecutableElement) { + val isPrivate by lazy { constructorElement.modifiers.contains(Modifier.PRIVATE) } + val parameters by lazy { constructorElement.parameters } + val fieldIds by lazy { + parameters.map { + it.annotationMirrors + .first { it.annotationType.toString() == "$SafeParcelable.Param" } + .elementValues + .filter { it.key.simpleName.toString() == "value" } + .firstNotNullOfOrNull { it.value } + .toString() + } + } + val argTypes by lazy { fieldIds.map { id -> clazz.fields.first { it.id == id }.type } } + val args by lazy { fieldIds.map { id -> clazz.fields.first { it.id == id }.variableName } } + val invocation by lazy { + if (isPrivate) { + listOf( + "Constructor<${clazz.fullName}> constructor = ${clazz.fullName}.class.getConstructor(${argTypes.map { "$it.class" }.joinToString(", ")});", + "constructor.setAccessible(true);", + "object = constructor.newInstance(${args.joinToString(", ")});" + ) + } else { + listOf("object = new ${clazz.fullName}(${args.joinToString(", ")});") + } + } +} + +class FieldInfo(val clazz: ClassInfo, val fieldElement: VariableElement) { + val name by lazy { fieldElement.simpleName.toString() } + val type by lazy { fieldElement.asType().toString() } + val isPrivate by lazy { fieldElement.modifiers.contains(Modifier.PRIVATE) } + + val id by lazy { + fieldElement.annotationMirrors + .first { it.annotationType.toString() == "$SafeParcelable.Field" } + .elementValues + .filter { it.key.simpleName.toString() == "value" } + .firstNotNullOfOrNull { it.value } + .toString() + } + + val variableName by lazy { "_$name\$000" } + val variableDeclaration by lazy { "$type $variableName;" } + val defaultValue by lazy { + when (type) { + "boolean" -> "false" + "byte", "char", "short", "int", "long", "float", "double" -> "0" + else -> "null" + } + } + val setVariableDefault by lazy { "$variableName = $defaultValue;" } + + val readVariableFromParcel by lazy { + when (type) { + "int" -> "$variableName = $SafeParcelReader.readInt(parcel, header)" + "long" -> "$variableName = $SafeParcelReader.readLong(parcel, header)" + "float" -> "$variableName = $SafeParcelReader.readFloat(parcel, header)" + "double" -> "$variableName = $SafeParcelReader.readDouble(parcel, header)" + else -> "$SafeParcelReader.skip(parcel, header)" + } + } + val readVariableFromParcelCase by lazy { "case $id: $readVariableFromParcel; break;" } + val writeVariableToParcel by lazy { "$SafeParcelWriter.write(parcel, $id, $variableName);" } + + val reflectionFieldName by lazy { "_${name}\$field" } + val reflectionFieldGetter by lazy { + when (type) { + "boolean" -> "$reflectionFieldName.getBoolean" + "byte" -> "$reflectionFieldName.getByte" + "char" -> "$reflectionFieldName.getChar" + "short" -> "$reflectionFieldName.getShort" + "int" -> "$reflectionFieldName.getInt" + "long" -> "$reflectionFieldName.getLong" + "float" -> "$reflectionFieldName.getFloat" + "double" -> "$reflectionFieldName.getDouble" + else -> "(${type}) $reflectionFieldName.get" + } + } + val reflectionFieldSetter by lazy { + when (type) { + "boolean" -> "$reflectionFieldName.setBoolean" + "byte" -> "$reflectionFieldName.setByte" + "char" -> "$reflectionFieldName.setChar" + "short" -> "$reflectionFieldName.setShort" + "int" -> "$reflectionFieldName.setInt" + "long" -> "$reflectionFieldName.setLong" + "float" -> "$reflectionFieldName.setFloat" + "double" -> "$reflectionFieldName.setDouble" + else -> "$reflectionFieldName.set" + } + } + + val setVariableFromField by lazy { + if (isPrivate) { + listOf( + "$Field $reflectionFieldName = ${clazz.fullName}.class.getDeclaredField(\"$name\");", + "$reflectionFieldName.setAccessible(true);", + "$variableName = $reflectionFieldGetter(object);" + ) + } else { + listOf("$variableName = object.$name;") + } + } + val setFieldFromVariable by lazy { + if (isPrivate) { + listOf( + "$Field $reflectionFieldName = ${clazz.fullName}.class.getDeclaredField(\"$name\");", + "$reflectionFieldName.setAccessible(true);", + "$reflectionFieldSetter(object, $variableName);" + ) + } else { + listOf("object.$name = $variableName;") + } + } +} \ No newline at end of file diff --git a/safe-parcel-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/safe-parcel-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000000..f3c1af8d8f --- /dev/null +++ b/safe-parcel-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1,6 @@ +# +# SPDX-FileCopyrightText: 2023 microG Project Team +# SPDX-License-Identifier: Apache-2.0 +# + +org.microg.safeparcel.SafeParcelProcessor \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7f72ad5025..0b46280437 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,11 +4,7 @@ def sublude(name) { project(projectName).projectDir = file(name.substring(1).replace(':', '/')) } -include ':play-services-basement' - -include ':play-services-tasks' - -include ':play-services-api' +include ':safe-parcel-processor' include ':play-services-ads' include ':play-services-ads-base' @@ -19,6 +15,7 @@ include ':play-services-auth' include ':play-services-auth-api-phone' include ':play-services-auth-base' include ':play-services-base' +include ':play-services-basement' include ':play-services-cast' include ':play-services-cast-framework' include ':play-services-clearcut' @@ -39,10 +36,12 @@ include ':play-services-places-placereport' include ':play-services-recaptcha' include ':play-services-safetynet' include ':play-services-tapandpay' +include ':play-services-tasks' include ':play-services-vision' include ':play-services-vision-common' include ':play-services-wearable' +include ':play-services-api' include ':play-services' include ':firebase-auth' From e73704f73629b71330b086f89a74da52853b37c8 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 17 Aug 2023 11:55:00 +0200 Subject: [PATCH 3/4] ads-identifier: Add most of the server side logic Except we don't persist, state is not shared accross apps and tracking is set to limited by default --- .../core/build.gradle | 4 + .../ads/identifier/AdvertisingIdService.kt | 122 ++++++++++++++++-- .../core/src/main/res/values/strings.xml | 11 ++ .../internal/IAdvertisingIdService.aidl | 15 ++- .../src/main/AndroidManifest.xml | 43 +++++- 5 files changed, 179 insertions(+), 16 deletions(-) create mode 100644 play-services-ads-identifier/core/src/main/res/values/strings.xml diff --git a/play-services-ads-identifier/core/build.gradle b/play-services-ads-identifier/core/build.gradle index 264eaf6691..bf27c84d20 100644 --- a/play-services-ads-identifier/core/build.gradle +++ b/play-services-ads-identifier/core/build.gradle @@ -29,4 +29,8 @@ android { sourceCompatibility = 1.8 targetCompatibility = 1.8 } + + lintOptions { + disable 'MissingTranslation' + } } diff --git a/play-services-ads-identifier/core/src/main/kotlin/org/microg/gms/ads/identifier/AdvertisingIdService.kt b/play-services-ads-identifier/core/src/main/kotlin/org/microg/gms/ads/identifier/AdvertisingIdService.kt index 1705116d4a..357538d9d3 100644 --- a/play-services-ads-identifier/core/src/main/kotlin/org/microg/gms/ads/identifier/AdvertisingIdService.kt +++ b/play-services-ads-identifier/core/src/main/kotlin/org/microg/gms/ads/identifier/AdvertisingIdService.kt @@ -5,30 +5,134 @@ package org.microg.gms.ads.identifier import android.app.Service +import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.os.Binder +import android.os.Bundle import android.os.IBinder +import android.util.Log +import androidx.core.os.bundleOf import com.google.android.gms.ads.identifier.internal.IAdvertisingIdService +import org.microg.gms.common.PackageUtils +import java.util.UUID + +const val TAG = "AdvertisingId" +const val EMPTY_AD_ID = "00000000-0000-0000-0000-000000000000" class AdvertisingIdService : Service() { override fun onBind(intent: Intent): IBinder? { - return AdvertisingIdServiceImpl().asBinder() + return AdvertisingIdServiceImpl(this).asBinder() + } +} + +class MemoryAdvertisingIdConfiguration(context: Context) : AdvertisingIdConfiguration(context) { + override val adTrackingLimitedPerApp: MutableMap = hashMapOf() + override var adTrackingLimitedGlobally: Boolean = true + override var debugLogging: Boolean = false + override var adId: String = EMPTY_AD_ID + override var debugAdId: String = EMPTY_AD_ID + + init { + resetAdvertisingId() + } +} + +abstract class AdvertisingIdConfiguration(private val context: Context) { + abstract val adTrackingLimitedPerApp: MutableMap + abstract var adTrackingLimitedGlobally: Boolean + abstract var debugLogging: Boolean + abstract var adId: String + abstract var debugAdId: String + + fun isAdTrackingLimitedForApp(uid: Int): Boolean { + if (adTrackingLimitedGlobally) return true + return adTrackingLimitedPerApp[uid] ?: false + } + + fun resetAdvertisingId(): String { + adId = UUID.randomUUID().toString() + debugAdId = UUID.randomUUID().toString().dropLast(12) + "10ca1ad1abe1" + return if (debugLogging) debugAdId else adId + } + + fun getAdvertisingIdForApp(uid: Int): String { + if (isAdTrackingLimitedForApp(uid)) return EMPTY_AD_ID + try { + val packageNames = context.packageManager.getPackagesForUid(uid) ?: return EMPTY_AD_ID + for (packageName in packageNames) { + val applicationInfo = context.packageManager.getApplicationInfo(packageName, 0) + if (applicationInfo.targetSdkVersion > 33) { + if (context.packageManager.checkPermission("com.google.android.gms.permission.AD_ID", packageName) == PackageManager.PERMISSION_DENIED) { + throw SecurityException("Permission not granted") + } + } + } + } catch (e: Exception) { + Log.w(TAG, "Permission check failed", e) + return EMPTY_AD_ID + } + val adId = if (debugLogging) debugAdId else adId + return adId.ifEmpty { resetAdvertisingId() } } } -class AdvertisingIdServiceImpl : IAdvertisingIdService.Stub() { +class AdvertisingIdServiceImpl(private val context: Context) : IAdvertisingIdService.Stub() { + private val configuration = MemoryAdvertisingIdConfiguration(context) + override fun getAdvertisingId(): String { - return "00000000-0000-0000-0000-000000000000" + return configuration.getAdvertisingIdForApp(Binder.getCallingUid()) + } + + override fun isAdTrackingLimited(ignored: Boolean): Boolean { + return configuration.isAdTrackingLimitedForApp(Binder.getCallingUid()) + } + + override fun resetAdvertisingId(packageName: String): String { + PackageUtils.checkPackageUid(context, packageName, getCallingUid()) + PackageUtils.assertExtendedAccess(context) + return configuration.resetAdvertisingId() + } + + override fun setAdTrackingLimitedGlobally(packageName: String, limited: Boolean) { + PackageUtils.checkPackageUid(context, packageName, getCallingUid()) + PackageUtils.assertExtendedAccess(context) + configuration.adTrackingLimitedGlobally = limited + } + + override fun setDebugLoggingEnabled(packageName: String, enabled: Boolean): String { + PackageUtils.checkPackageUid(context, packageName, getCallingUid()) + PackageUtils.assertExtendedAccess(context) + configuration.debugLogging = enabled + return advertisingId + } + + override fun isDebugLoggingEnabled(): Boolean { + return configuration.debugLogging + } + + override fun isAdTrackingLimitedGlobally(): Boolean { + PackageUtils.assertExtendedAccess(context) + return configuration.adTrackingLimitedGlobally + } + + override fun setAdTrackingLimitedForApp(uid: Int, limited: Boolean) { + PackageUtils.assertExtendedAccess(context) + configuration.adTrackingLimitedPerApp[uid] = limited } - override fun isAdTrackingLimited(defaultHint: Boolean): Boolean { - return true + override fun resetAdTrackingLimitedForApp(uid: Int) { + PackageUtils.assertExtendedAccess(context) + configuration.adTrackingLimitedPerApp.remove(uid) } - override fun generateAdvertisingId(packageName: String): String { - return advertisingId // Ad tracking limited + override fun getAllAppsLimitedAdTrackingConfiguration(): Bundle { + PackageUtils.assertExtendedAccess(context) + return bundleOf(*configuration.adTrackingLimitedPerApp.map { it.key.toString() to it.value }.toTypedArray()) } - override fun setAdTrackingLimited(packageName: String, limited: Boolean) { - // Ignored, sorry :) + override fun getAdvertisingIdForApp(uid: Int): String { + PackageUtils.assertExtendedAccess(context) + return configuration.getAdvertisingIdForApp(uid) } } diff --git a/play-services-ads-identifier/core/src/main/res/values/strings.xml b/play-services-ads-identifier/core/src/main/res/values/strings.xml new file mode 100644 index 0000000000..865dd6a11b --- /dev/null +++ b/play-services-ads-identifier/core/src/main/res/values/strings.xml @@ -0,0 +1,11 @@ + + + + Advertising ID Permission + Allows a publisher app to access a valid advertising ID directly or indirectly. + Advertising ID notification + Allows an app to receive a notification when the advertising ID or limit ad tracking preference of the user is updated. + \ No newline at end of file diff --git a/play-services-ads-identifier/src/main/aidl/com/google/android/gms/ads/identifier/internal/IAdvertisingIdService.aidl b/play-services-ads-identifier/src/main/aidl/com/google/android/gms/ads/identifier/internal/IAdvertisingIdService.aidl index 5f4f5c1f4d..bda8695f2a 100644 --- a/play-services-ads-identifier/src/main/aidl/com/google/android/gms/ads/identifier/internal/IAdvertisingIdService.aidl +++ b/play-services-ads-identifier/src/main/aidl/com/google/android/gms/ads/identifier/internal/IAdvertisingIdService.aidl @@ -1,8 +1,17 @@ package com.google.android.gms.ads.identifier.internal; +import android.os.Bundle; + interface IAdvertisingIdService { String getAdvertisingId() = 0; - boolean isAdTrackingLimited(boolean defaultHint) = 1; - String generateAdvertisingId(String packageName) = 2; - void setAdTrackingLimited(String packageName, boolean limited) = 3; + boolean isAdTrackingLimited(boolean ignored) = 1; + String resetAdvertisingId(String packageName) = 2; + void setAdTrackingLimitedGlobally(String packageName, boolean limited) = 3; + String setDebugLoggingEnabled(String packageName, boolean enabled) = 4; + boolean isDebugLoggingEnabled() = 5; + boolean isAdTrackingLimitedGlobally() = 6; + void setAdTrackingLimitedForApp(int uid, boolean limited) = 7; + void resetAdTrackingLimitedForApp(int uid) = 8; + Bundle getAllAppsLimitedAdTrackingConfiguration() = 9; // Map packageName -> Boolean + String getAdvertisingIdForApp(int uid) = 10; } diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 67c9bf79cb..5ff4980dee 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - - + + + + + + + + + + + + + + + + + + + Date: Fri, 18 Aug 2023 10:41:58 +0200 Subject: [PATCH 4/4] Add basic dummy for Google Help and Feedback --- .../android/gms/feedback/FeedbackOptions.aidl | 3 + .../android/gms/googlehelp/GoogleHelp.aidl | 3 + .../android/gms/googlehelp/InProductHelp.aidl | 3 + .../gms/googlehelp/SupportRequestHelp.aidl | 3 + .../internal/common/IGoogleHelpCallbacks.aidl | 26 ++++ .../internal/common/IGoogleHelpService.aidl | 26 ++++ .../android/gms/feedback/ErrorReport.java | 74 ++++++++++ .../android/gms/feedback/FeedbackOptions.java | 39 ++++++ .../android/gms/feedback/FileTeleporter.java | 12 ++ .../android/gms/feedback/LogOptions.java | 12 ++ .../android/gms/feedback/ThemeSettings.java | 28 ++++ .../FRDProductSpecificDataEntry.java | 12 ++ .../android/gms/googlehelp/GoogleHelp.java | 131 ++++++++++++++++++ .../android/gms/googlehelp/InProductHelp.java | 14 ++ .../android/gms/googlehelp/ND4CSettings.java | 12 ++ .../gms/googlehelp/OfflineSuggestion.java | 12 ++ .../gms/googlehelp/SupportRequestHelp.java | 17 +++ .../internal/common/OverflowMenuItem.java | 12 ++ .../internal/common/TogglingData.java | 12 ++ .../gms/googlehelp/GoogleHelpService.kt | 106 ++++++++++++++ .../ui/GoogleHelpRedirectActivity.kt | 37 +++++ 21 files changed, 594 insertions(+) create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/feedback/FeedbackOptions.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/googlehelp/GoogleHelp.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/googlehelp/InProductHelp.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/googlehelp/SupportRequestHelp.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/googlehelp/internal/common/IGoogleHelpCallbacks.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/googlehelp/internal/common/IGoogleHelpService.aidl create mode 100644 play-services-api/src/main/java/com/google/android/gms/feedback/ErrorReport.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/feedback/FeedbackOptions.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/feedback/FileTeleporter.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/feedback/LogOptions.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/feedback/ThemeSettings.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/googlehelp/FRDProductSpecificDataEntry.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/googlehelp/GoogleHelp.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/googlehelp/InProductHelp.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/googlehelp/ND4CSettings.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/googlehelp/OfflineSuggestion.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/googlehelp/SupportRequestHelp.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/googlehelp/internal/common/OverflowMenuItem.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/googlehelp/internal/common/TogglingData.java create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/googlehelp/GoogleHelpService.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/googlehelp/ui/GoogleHelpRedirectActivity.kt diff --git a/play-services-api/src/main/aidl/com/google/android/gms/feedback/FeedbackOptions.aidl b/play-services-api/src/main/aidl/com/google/android/gms/feedback/FeedbackOptions.aidl new file mode 100644 index 0000000000..8e90ece2bd --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/feedback/FeedbackOptions.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.feedback; + +parcelable FeedbackOptions; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/GoogleHelp.aidl b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/GoogleHelp.aidl new file mode 100644 index 0000000000..9da8ad736b --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/GoogleHelp.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.googlehelp; + +parcelable GoogleHelp; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/InProductHelp.aidl b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/InProductHelp.aidl new file mode 100644 index 0000000000..717af5b3e0 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/InProductHelp.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.googlehelp; + +parcelable InProductHelp; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/SupportRequestHelp.aidl b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/SupportRequestHelp.aidl new file mode 100644 index 0000000000..4093b9cc2a --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/SupportRequestHelp.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.googlehelp; + +parcelable SupportRequestHelp; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/internal/common/IGoogleHelpCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/internal/common/IGoogleHelpCallbacks.aidl new file mode 100644 index 0000000000..777fbf53df --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/internal/common/IGoogleHelpCallbacks.aidl @@ -0,0 +1,26 @@ +package com.google.android.gms.googlehelp.internal.common; + +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; + +import com.google.android.gms.googlehelp.GoogleHelp; +import com.google.android.gms.googlehelp.InProductHelp; + +interface IGoogleHelpCallbacks { + void onProcessGoogleHelpFinished(in GoogleHelp googleHelp) = 0; + oneway void onSaveAsyncPsdFinished() = 6; + oneway void onSaveAsyncPsbdFinished() = 7; + void onRequestChatSupportSuccess(int chatQueuePosition) = 8; + void onRequestChatSupportFailed() = 9; + void onRequestC2cSupportSuccess() = 10; + void onRequestC2cSupportFailed() = 11; + void onSuggestions(in byte[] suggestions) = 12; + void onNoSuggestions() = 13; + void onEscalationOptions(in byte[] options) = 14; + void onNoEscalationOptions() = 15; + void onProcessInProductHelpFinished(in InProductHelp inProductHelp) = 16; + void onRealtimeSupportStatus(in byte[] status) = 17; + void onNoRealtimeSupportStatus() = 18; +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/internal/common/IGoogleHelpService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/internal/common/IGoogleHelpService.aidl new file mode 100644 index 0000000000..6fbb2a2581 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/googlehelp/internal/common/IGoogleHelpService.aidl @@ -0,0 +1,26 @@ +package com.google.android.gms.googlehelp.internal.common; + +import android.graphics.Bitmap; +import android.os.Bundle; + +import com.google.android.gms.feedback.FeedbackOptions; +import com.google.android.gms.googlehelp.GoogleHelp; +import com.google.android.gms.googlehelp.InProductHelp; +import com.google.android.gms.googlehelp.SupportRequestHelp; +import com.google.android.gms.googlehelp.internal.common.IGoogleHelpCallbacks; + +interface IGoogleHelpService { + void processGoogleHelpAndPip(in GoogleHelp googleHelp, IGoogleHelpCallbacks callbacks) = 0; + void processGoogleHelpAndPipWithBitmap(in GoogleHelp googleHelp, in Bitmap bitmap, IGoogleHelpCallbacks callbacks) = 1; + oneway void saveAsyncHelpPsd(in Bundle bundle, long timestamp, in GoogleHelp googleHelp, IGoogleHelpCallbacks callbacks) = 7; + oneway void saveAsyncFeedbackPsd(in Bundle bundle, long timestamp, in GoogleHelp googleHelp, IGoogleHelpCallbacks callbacks) = 8; + oneway void saveAsyncFeedbackPsbd(in FeedbackOptions options, in Bundle bundle, long timestamp, in GoogleHelp googleHelp, IGoogleHelpCallbacks callbacks) = 9; + oneway void requestChatSupport(in GoogleHelp googleHelp, String phoneNumber, String s2, IGoogleHelpCallbacks callbacks) = 10; + oneway void requestC2cSupport(in GoogleHelp googleHelp, String phoneNumber, String s2, IGoogleHelpCallbacks callbacks) = 11; + oneway void getSuggestions(in GoogleHelp googleHelp, IGoogleHelpCallbacks callbacks) = 12; + oneway void getEscalationOptions(in GoogleHelp googleHelp, IGoogleHelpCallbacks callbacks) = 13; + oneway void requestChatSupportWithSupportRequest(in SupportRequestHelp supportRequestHelp, IGoogleHelpCallbacks callbacks) = 14; + oneway void requestC2cSupportWithSupportRequest(in SupportRequestHelp supportRequestHelp, IGoogleHelpCallbacks callbacks) = 15; + void processInProductHelpAndPip(in InProductHelp inProductHelp, in Bitmap bitmap, IGoogleHelpCallbacks callbacks) = 16; + oneway void getRealtimeSupportStatus(in GoogleHelp googleHelp, IGoogleHelpCallbacks callbacks) = 17; +} diff --git a/play-services-api/src/main/java/com/google/android/gms/feedback/ErrorReport.java b/play-services-api/src/main/java/com/google/android/gms/feedback/ErrorReport.java new file mode 100644 index 0000000000..1db6be7fda --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/feedback/ErrorReport.java @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.feedback; + +import android.app.ApplicationErrorReport; +import android.graphics.Bitmap; +import org.microg.safeparcel.AutoSafeParcelable; + +public class ErrorReport extends AutoSafeParcelable { + @Field(2) + public ApplicationErrorReport applicationErrorReport; + @Field(4) + public int unknownInt4; + @Field(12) + public int unknownInt12; + @Field(24) + public int unknownInt24; + @Field(25) + public int unknownInt25; + @Field(26) + public int unknownInt26; + @Field(27) + public int unknownInt27; + @Field(32) + public boolean unknownBool32; + @Field(33) + public int unknownInt33; + @Field(34) + public int unknownInt34; + @Field(35) + public boolean unknownBool35; + @Field(36) + public String exceptionClassName; + @Field(37) + public String throwFileName; + @Field(38) + public int throwLineNumber; + @Field(39) + public String throwClassName; + @Field(40) + public String throwMethodName; + @Field(41) + public String stackTrace; + @Field(42) + public String exceptionMessage; + @Field(50) + public boolean unknownBool50; + @Field(51) + public String unknownString51; + @Field(52) + public ThemeSettings themeSettings; + @Field(53) + public LogOptions logOptions; + @Field(55) + public boolean unknownBool55; + @Field(58) + public boolean unknownBool58; + @Field(59) + public Bitmap unknownBitmap59; + @Field(60) + public String unknownString60; + @Field(62) + public int unknownInt62; + @Field(63) + public int unknownInt63; + @Field(67) + public boolean unknownBool67; + @Field(68) + public boolean unknownBool68; + public static final Creator CREATOR = findCreator(ErrorReport.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/feedback/FeedbackOptions.java b/play-services-api/src/main/java/com/google/android/gms/feedback/FeedbackOptions.java new file mode 100644 index 0000000000..2532fe048b --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/feedback/FeedbackOptions.java @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.feedback; + +import android.app.ApplicationErrorReport; +import android.graphics.Bitmap; +import android.os.Bundle; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.List; + +public class FeedbackOptions extends AutoSafeParcelable { + @Field(3) + public Bundle unknownBundle3; + @Field(6) + public ApplicationErrorReport applicationErrorReport; + @Field(9) + public String packageName; + @Field(10) + public List files; + @Field(11) + public boolean unknownBool11; + @Field(12) + public ThemeSettings themeSettings; + @Field(13) + public LogOptions logOptions; + @Field(15) + public Bitmap screenshot; + @Field(17) + public boolean unknownBool17; + @Field(18) + public long unknownLong18; + @Field(19) + public boolean unknownBool19; + public static final Creator CREATOR = findCreator(FeedbackOptions.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/feedback/FileTeleporter.java b/play-services-api/src/main/java/com/google/android/gms/feedback/FileTeleporter.java new file mode 100644 index 0000000000..73206ef252 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/feedback/FileTeleporter.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.feedback; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class FileTeleporter extends AutoSafeParcelable { + public static final Creator CREATOR = findCreator(FileTeleporter.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/feedback/LogOptions.java b/play-services-api/src/main/java/com/google/android/gms/feedback/LogOptions.java new file mode 100644 index 0000000000..374415058f --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/feedback/LogOptions.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.feedback; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class LogOptions extends AutoSafeParcelable { + public static final Creator CREATOR = findCreator(LogOptions.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/feedback/ThemeSettings.java b/play-services-api/src/main/java/com/google/android/gms/feedback/ThemeSettings.java new file mode 100644 index 0000000000..3dee3f0a86 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/feedback/ThemeSettings.java @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.feedback; + +import androidx.annotation.NonNull; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +public class ThemeSettings extends AutoSafeParcelable { + @Field(2) + public int unknownInt2; + @Field(3) + public int unknownInt3; + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("ThemeSettings") + .field("2", unknownInt2) + .field("3", unknownInt3) + .end(); + } + + public static final Creator CREATOR = findCreator(ThemeSettings.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/googlehelp/FRDProductSpecificDataEntry.java b/play-services-api/src/main/java/com/google/android/gms/googlehelp/FRDProductSpecificDataEntry.java new file mode 100644 index 0000000000..cf3bfd4ce4 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/googlehelp/FRDProductSpecificDataEntry.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.googlehelp; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class FRDProductSpecificDataEntry extends AutoSafeParcelable { + public static final Creator CREATOR = findCreator(FRDProductSpecificDataEntry.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/googlehelp/GoogleHelp.java b/play-services-api/src/main/java/com/google/android/gms/googlehelp/GoogleHelp.java new file mode 100644 index 0000000000..d189a2a398 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/googlehelp/GoogleHelp.java @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.googlehelp; + +import android.accounts.Account; +import android.app.PendingIntent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import androidx.annotation.NonNull; +import com.google.android.gms.feedback.ErrorReport; +import com.google.android.gms.feedback.ThemeSettings; +import com.google.android.gms.googlehelp.internal.common.OverflowMenuItem; +import com.google.android.gms.googlehelp.internal.common.TogglingData; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.List; + +public class GoogleHelp extends AutoSafeParcelable { + @Field(1) + private int versionCode; + @Field(2) + public String appContext; + @Field(3) + public Account account; + @Field(4) + public Bundle extras; + @Field(5) + public boolean unknownBool5; + @Field(6) + public boolean unknownBool6; + @Field(7) + public List unknownStringList7; + @Field(15) + public Uri uri; + @Field(16) + public List overflowMenuItems; + @Field(17) + public int unknownAlwaysZero17; + @Field(18) + public List offlineSuggestions; + @Field(20) + public int unknownInt20; + @Field(21) + public int unknownInt21; + @Field(22) + public boolean unknownBool22; + @Field(23) + public ErrorReport errorReport; + @Field(25) + public ThemeSettings themeSettings; + @Field(28) + public String appPackageName; + @Field(31) + public TogglingData togglingData; + @Field(32) + public int unknownInt32; + @Field(33) + public PendingIntent customFeedbackPendingIntent; + @Field(34) + public String title; + @Field(35) + public Bitmap icon; + @Field(36) + public int unknownInt36; + @Field(37) + public boolean unknownBool37; + @Field(38) + public boolean unknownBool38; + @Field(39) + public int timeout; + @Field(40) + public String sessionId; + @Field(41) + public boolean unknownBool41; + @Field(42) + public String clientPackageName; + @Field(43) + public boolean unknownBool43; + @Field(44) + public ND4CSettings nd4CSettings; + @Field(45) + public boolean unknownBool45; + @Field(46) + public List productSpecificDataEntries; + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("GoogleHelp") + .field("appContext", appContext) + .field("account", account) + .field("extras", extras) + .field("5", unknownBool5) + .field("6", unknownBool6) + .field("7", unknownStringList7) + .field("uri", uri) + .field("overflowMenuItems", overflowMenuItems) + .field("17", unknownAlwaysZero17) + .field("offlineSuggestions", offlineSuggestions) + .field("20", unknownInt20) + .field("21", unknownInt21) + .field("22", unknownBool22) + .field("errorReport", errorReport) + .field("themeSettings", themeSettings) + .field("appPackageName", appPackageName) + .field("togglingData", togglingData) + .field("32", unknownInt32) + .field("customFeedbackPendingIntent", customFeedbackPendingIntent) + .field("title", title) + .field("icon", icon) + .field("36", unknownInt36) + .field("37", unknownBool37) + .field("38", unknownBool38) + .field("timeout", timeout) + .field("sessionId", sessionId) + .field("41", unknownBool41) + .field("clientPackageName", clientPackageName) + .field("43", unknownBool43) + .field("nd4CSettings", nd4CSettings) + .field("45", unknownBool45) + .field("productSpecificDataEntries", productSpecificDataEntries) + .end(); + } + + public static final Creator CREATOR = findCreator(GoogleHelp.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/googlehelp/InProductHelp.java b/play-services-api/src/main/java/com/google/android/gms/googlehelp/InProductHelp.java new file mode 100644 index 0000000000..141a168bef --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/googlehelp/InProductHelp.java @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.googlehelp; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class InProductHelp extends AutoSafeParcelable { + @Field(1) + public GoogleHelp googleHelp; + public static final Creator CREATOR = findCreator(InProductHelp.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/googlehelp/ND4CSettings.java b/play-services-api/src/main/java/com/google/android/gms/googlehelp/ND4CSettings.java new file mode 100644 index 0000000000..c16a0e18a8 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/googlehelp/ND4CSettings.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.googlehelp; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class ND4CSettings extends AutoSafeParcelable { + public static final Creator CREATOR = findCreator(ND4CSettings.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/googlehelp/OfflineSuggestion.java b/play-services-api/src/main/java/com/google/android/gms/googlehelp/OfflineSuggestion.java new file mode 100644 index 0000000000..952164ada5 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/googlehelp/OfflineSuggestion.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.googlehelp; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class OfflineSuggestion extends AutoSafeParcelable { + public static final Creator CREATOR = findCreator(OfflineSuggestion.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/googlehelp/SupportRequestHelp.java b/play-services-api/src/main/java/com/google/android/gms/googlehelp/SupportRequestHelp.java new file mode 100644 index 0000000000..4fd8d354b5 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/googlehelp/SupportRequestHelp.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.googlehelp; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class SupportRequestHelp extends AutoSafeParcelable { + @Field(1) + public GoogleHelp googleHelp; + @Field(4) + public String phoneNumber; + + public static final Creator CREATOR = findCreator(SupportRequestHelp.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/googlehelp/internal/common/OverflowMenuItem.java b/play-services-api/src/main/java/com/google/android/gms/googlehelp/internal/common/OverflowMenuItem.java new file mode 100644 index 0000000000..e966f7c38e --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/googlehelp/internal/common/OverflowMenuItem.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.googlehelp.internal.common; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class OverflowMenuItem extends AutoSafeParcelable { + public static final Creator CREATOR = findCreator(OverflowMenuItem.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/googlehelp/internal/common/TogglingData.java b/play-services-api/src/main/java/com/google/android/gms/googlehelp/internal/common/TogglingData.java new file mode 100644 index 0000000000..f2e21c8b54 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/googlehelp/internal/common/TogglingData.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.googlehelp.internal.common; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class TogglingData extends AutoSafeParcelable { + public static final Creator CREATOR = findCreator(TogglingData.class); +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/GoogleHelpService.kt b/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/GoogleHelpService.kt new file mode 100644 index 0000000000..2faa31ae46 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/GoogleHelpService.kt @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.googlehelp + +import android.graphics.Bitmap +import android.os.Bundle +import android.util.Log +import com.google.android.gms.common.Feature +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.feedback.FeedbackOptions +import com.google.android.gms.googlehelp.GoogleHelp +import com.google.android.gms.googlehelp.InProductHelp +import com.google.android.gms.googlehelp.SupportRequestHelp +import com.google.android.gms.googlehelp.internal.common.IGoogleHelpCallbacks +import com.google.android.gms.googlehelp.internal.common.IGoogleHelpService +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils + +private const val TAG = "GoogleHelp" + +val FEATURES = arrayOf( + Feature("user_service_support", 1) +) + +class GoogleHelpService : BaseService(TAG, GmsService.HELP) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) + ?: throw IllegalArgumentException("Missing package name") + val binder = GoogleHelpServiceImpl(packageName).asBinder() + callback.onPostInitCompleteWithConnectionInfo(CommonStatusCodes.SUCCESS, binder, ConnectionInfo().apply { features = FEATURES }) + } +} + +class GoogleHelpServiceImpl(val packageName: String) : IGoogleHelpService.Stub() { + override fun processGoogleHelpAndPip(googleHelp: GoogleHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: processGoogleHelpAndPip($googleHelp)") + // Strip error report, can be too big + googleHelp?.errorReport = null + callbacks?.onProcessGoogleHelpFinished(googleHelp) + } + + override fun processGoogleHelpAndPipWithBitmap(googleHelp: GoogleHelp?, bitmap: Bitmap?, callbacks: IGoogleHelpCallbacks?) { + processGoogleHelpAndPip(googleHelp, callbacks) + } + + override fun saveAsyncHelpPsd(bundle: Bundle?, timestamp: Long, googleHelp: GoogleHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: saveAsyncHelpPsd") + callbacks?.onSaveAsyncPsdFinished() + } + + override fun saveAsyncFeedbackPsd(bundle: Bundle?, timestamp: Long, googleHelp: GoogleHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: saveAsyncFeedbackPsd") + callbacks?.onSaveAsyncPsdFinished() + } + + override fun saveAsyncFeedbackPsbd(options: FeedbackOptions?, bundle: Bundle?, timestamp: Long, googleHelp: GoogleHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: saveAsyncFeedbackPsbd") + callbacks?.onSaveAsyncPsbdFinished() + } + + override fun requestChatSupport(googleHelp: GoogleHelp?, phoneNumber: String?, s2: String?, callbacks: IGoogleHelpCallbacks?) { + requestChatSupportWithSupportRequest(SupportRequestHelp().also { it.googleHelp = googleHelp; it.phoneNumber = phoneNumber }, callbacks) + } + + override fun requestC2cSupport(googleHelp: GoogleHelp?, phoneNumber: String?, s2: String?, callbacks: IGoogleHelpCallbacks?) { + requestC2cSupportWithSupportRequest(SupportRequestHelp().also { it.googleHelp = googleHelp; it.phoneNumber = phoneNumber }, callbacks) + } + + override fun getSuggestions(googleHelp: GoogleHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: getSuggestions") + callbacks?.onNoSuggestions() + } + + override fun getEscalationOptions(googleHelp: GoogleHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: getEscalationOptions") + callbacks?.onNoEscalationOptions() + } + + override fun requestChatSupportWithSupportRequest(supportRequestHelp: SupportRequestHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: requestChatSupport") + callbacks?.onRequestChatSupportFailed() + } + + override fun requestC2cSupportWithSupportRequest(supportRequestHelp: SupportRequestHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: requestC2cSupport") + callbacks?.onRequestC2cSupportFailed() + } + + override fun processInProductHelpAndPip(inProductHelp: InProductHelp?, bitmap: Bitmap?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: processInProductHelpAndPip") + callbacks?.onProcessInProductHelpFinished(inProductHelp) + } + + override fun getRealtimeSupportStatus(googleHelp: GoogleHelp?, callbacks: IGoogleHelpCallbacks?) { + Log.d(TAG, "Not yet implemented: getRealtimeSupportStatus") + callbacks?.onNoRealtimeSupportStatus() + } + +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/ui/GoogleHelpRedirectActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/ui/GoogleHelpRedirectActivity.kt new file mode 100644 index 0000000000..10528c6d3b --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/ui/GoogleHelpRedirectActivity.kt @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.googlehelp.ui + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import com.google.android.gms.googlehelp.GoogleHelp + +private const val TAG = "GoogleHelpRedirect" + +class GoogleHelpRedirectActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val callingPackage = callingPackage ?: return finish() + val intent = intent ?: return finish() + val googleHelp = intent.getParcelableExtra(EXTRA_GOOGLE_HELP) ?: return finish() + Log.d(TAG, "Using GoogleHelp: $googleHelp") + val uri = googleHelp.uri ?: return finish() + // TODO: Not all Google apps send proper URI values, as these are in fact not used by Google's original implementation. + // As a work-around we should get the proper URL by retrieving top_level_topic_url:$callingPackage + // from https://www.google.com/tools/feedback/mobile/get-configurations endpoint. + // Long-term best is to not redirect to web but instead implement the thing properly, allowing us also to show + // option items, do proper theming for better integration, etc. + Log.d(TAG, "Open $uri for $callingPackage/${googleHelp.appContext} in Browser") + startActivity(Intent(Intent.ACTION_VIEW, uri)) + finish() + } + + companion object { + const val EXTRA_GOOGLE_HELP = "EXTRA_GOOGLE_HELP" + } +} \ No newline at end of file