diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/CachedSpotDevice.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/CachedSpotDevice.aidl new file mode 100644 index 0000000000..01155d41af --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/CachedSpotDevice.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable CachedSpotDevice; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsRequest.aidl new file mode 100644 index 0000000000..318d101e96 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable ChangeFindMyDeviceSettingsRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsResponse.aidl new file mode 100644 index 0000000000..8554ce2b20 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable ChangeFindMyDeviceSettingsResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/DisableLocationReportingRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/DisableLocationReportingRequest.aidl new file mode 100644 index 0000000000..ee5f910a76 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/DisableLocationReportingRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable DisableLocationReportingRequest; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/DisableLocationReportingResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/DisableLocationReportingResponse.aidl new file mode 100644 index 0000000000..7086344f66 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/DisableLocationReportingResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable DisableLocationReportingResponse; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/FindMyDeviceNetworkSettings.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/FindMyDeviceNetworkSettings.aidl new file mode 100644 index 0000000000..106b221a0c --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/FindMyDeviceNetworkSettings.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable FindMyDeviceNetworkSettings; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetCachedDevicesRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetCachedDevicesRequest.aidl new file mode 100644 index 0000000000..6c8d1156e5 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetCachedDevicesRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetCachedDevicesRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetCachedDevicesResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetCachedDevicesResponse.aidl new file mode 100644 index 0000000000..e3db0c51c0 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetCachedDevicesResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetCachedDevicesResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsRequest.aidl new file mode 100644 index 0000000000..a5eba7fac5 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetFindMyDeviceSettingsRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsResponse.aidl new file mode 100644 index 0000000000..8541cd0301 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetFindMyDeviceSettingsResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportRequest.aidl new file mode 100644 index 0000000000..49032aebb6 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetKeychainLockScreenKnowledgeFactorSupportRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportResponse.aidl new file mode 100644 index 0000000000..1b49e2c258 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetKeychainLockScreenKnowledgeFactorSupportResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetLocationReportingStateRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetLocationReportingStateRequest.aidl new file mode 100644 index 0000000000..c256e6bbce --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetLocationReportingStateRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetLocationReportingStateRequest; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetLocationReportingStateResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetLocationReportingStateResponse.aidl new file mode 100644 index 0000000000..9cef85a672 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetLocationReportingStateResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetLocationReportingStateResponse; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetOwnerKeyRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetOwnerKeyRequest.aidl new file mode 100644 index 0000000000..81b74679b3 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetOwnerKeyRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetOwnerKeyRequest; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetOwnerKeyResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetOwnerKeyResponse.aidl new file mode 100644 index 0000000000..1da737ca6a --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/GetOwnerKeyResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable GetOwnerKeyResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyRequest.aidl new file mode 100644 index 0000000000..f34c0f0de9 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable ImportGivenOwnerKeyRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyResponse.aidl new file mode 100644 index 0000000000..c2be793c6b --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable ImportGivenOwnerKeyResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysRequest.aidl new file mode 100644 index 0000000000..d4911b3e07 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable ImportRequiredOwnerKeysRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysResponse.aidl new file mode 100644 index 0000000000..59eebeaccf --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable ImportRequiredOwnerKeysResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/LocationReportRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/LocationReportRequest.aidl new file mode 100644 index 0000000000..306c2f9ddd --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/LocationReportRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable LocationReportRequest; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/LocationReportResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/LocationReportResponse.aidl new file mode 100644 index 0000000000..6636558eb5 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/LocationReportResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable LocationReportResponse; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/OwnersLocationReportRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/OwnersLocationReportRequest.aidl new file mode 100644 index 0000000000..59f354f532 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/OwnersLocationReportRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable OwnersLocationReportRequest; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/OwnersLocationReportResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/OwnersLocationReportResponse.aidl new file mode 100644 index 0000000000..eff66485cb --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/OwnersLocationReportResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable OwnersLocationReportResponse; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ScanResult.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ScanResult.aidl new file mode 100644 index 0000000000..30f2f65e53 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/ScanResult.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable ScanResult; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SetOwnerKeyRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SetOwnerKeyRequest.aidl new file mode 100644 index 0000000000..330c3bcdbe --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SetOwnerKeyRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable SetOwnerKeyRequest; \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SetOwnerKeyResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SetOwnerKeyResponse.aidl new file mode 100644 index 0000000000..983196beac --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SetOwnerKeyResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable SetOwnerKeyResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SyncOwnerKeyRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SyncOwnerKeyRequest.aidl new file mode 100644 index 0000000000..632eebedc0 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SyncOwnerKeyRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable SyncOwnerKeyRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SyncOwnerKeyResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SyncOwnerKeyResponse.aidl new file mode 100644 index 0000000000..2caa55fb10 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/SyncOwnerKeyResponse.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +parcelable SyncOwnerKeyResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotLocationReportCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotLocationReportCallbacks.aidl new file mode 100644 index 0000000000..32d8eea524 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotLocationReportCallbacks.aidl @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot.internal; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.findmydevice.spot.LocationReportResponse; +import com.google.android.gms.findmydevice.spot.OwnersLocationReportResponse; +import com.google.android.gms.findmydevice.spot.GetLocationReportingStateResponse; +import com.google.android.gms.findmydevice.spot.DisableLocationReportingResponse; + +interface ISpotLocationReportCallbacks { + void onLocationReport(in Status status, in LocationReportResponse response) = 2; + void onOwnersLocationReport(in Status status, in OwnersLocationReportResponse response) = 4; + void onGetLocationReportingState(in Status status, in GetLocationReportingStateResponse response) = 6; + void onDisableLocationReporting(in Status status, in DisableLocationReportingResponse response) = 7; +} \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotLocationReportService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotLocationReportService.aidl new file mode 100644 index 0000000000..3e8bf7a535 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotLocationReportService.aidl @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot.internal; + +import com.google.android.gms.findmydevice.spot.internal.ISpotLocationReportCallbacks; +import com.google.android.gms.findmydevice.spot.LocationReportRequest; +import com.google.android.gms.findmydevice.spot.GetLocationReportingStateRequest; +import com.google.android.gms.findmydevice.spot.DisableLocationReportingRequest; + +interface ISpotLocationReportService { + void locationReport(ISpotLocationReportCallbacks callbacks, in LocationReportRequest request) = 2; + void getLocationReportingState(ISpotLocationReportCallbacks callbacks, in GetLocationReportingStateRequest request) = 6; + void disableLocationReporting(ISpotLocationReportCallbacks callbacks, in DisableLocationReportingRequest request) = 7; +} \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotManagementCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotManagementCallbacks.aidl new file mode 100644 index 0000000000..359bfea64e --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotManagementCallbacks.aidl @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot.internal; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.findmydevice.spot.ChangeFindMyDeviceSettingsResponse; +import com.google.android.gms.findmydevice.spot.GetCachedDevicesResponse; +import com.google.android.gms.findmydevice.spot.GetFindMyDeviceSettingsResponse; +import com.google.android.gms.findmydevice.spot.GetKeychainLockScreenKnowledgeFactorSupportResponse; +import com.google.android.gms.findmydevice.spot.GetOwnerKeyResponse; +import com.google.android.gms.findmydevice.spot.ImportGivenOwnerKeyResponse; +import com.google.android.gms.findmydevice.spot.ImportRequiredOwnerKeysResponse; +import com.google.android.gms.findmydevice.spot.SetOwnerKeyResponse; +import com.google.android.gms.findmydevice.spot.SyncOwnerKeyResponse; + +interface ISpotManagementCallbacks { + void onGetOwnerKey(in Status status, in GetOwnerKeyResponse response) = 0; + void onSetOwnerKey(in Status status, in SetOwnerKeyResponse response) = 1; + void onImportRequiredOwnerKeys(in Status status, in ImportRequiredOwnerKeysResponse response) = 2; + void onSyncOwnerKey(in Status status, in SyncOwnerKeyResponse response) = 3; + void onImportGivenOwnerKey(in Status status, in ImportGivenOwnerKeyResponse response) = 4; + void onGetFindMyDeviceSettings(in Status status, in GetFindMyDeviceSettingsResponse response) = 5; + void onChangeFindMyDeviceSettings(in Status status, in ChangeFindMyDeviceSettingsResponse response) = 6; + void onGetKeychainLockScreenKnowledgeFactorSupport(in Status status, in GetKeychainLockScreenKnowledgeFactorSupportResponse response) = 7; + void onGetCachedDevices(in Status status, in GetCachedDevicesResponse response) = 8; +} + + + diff --git a/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotManagementService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotManagementService.aidl new file mode 100644 index 0000000000..eea95ed4db --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/findmydevice/spot/internal/ISpotManagementService.aidl @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot.internal; + +import com.google.android.gms.findmydevice.spot.internal.ISpotManagementCallbacks; +import com.google.android.gms.findmydevice.spot.ChangeFindMyDeviceSettingsRequest; +import com.google.android.gms.findmydevice.spot.GetCachedDevicesRequest; +import com.google.android.gms.findmydevice.spot.GetFindMyDeviceSettingsRequest; +import com.google.android.gms.findmydevice.spot.GetKeychainLockScreenKnowledgeFactorSupportRequest; +import com.google.android.gms.findmydevice.spot.GetOwnerKeyRequest; +import com.google.android.gms.findmydevice.spot.ImportGivenOwnerKeyRequest; +import com.google.android.gms.findmydevice.spot.ImportRequiredOwnerKeysRequest; +import com.google.android.gms.findmydevice.spot.SetOwnerKeyRequest; +import com.google.android.gms.findmydevice.spot.SyncOwnerKeyRequest; + +interface ISpotManagementService { + void getOwnerKey(ISpotManagementCallbacks callbacks, in GetOwnerKeyRequest request) = 0; + void setOwnerKey(ISpotManagementCallbacks callbacks, in SetOwnerKeyRequest request) = 1; + void importRequiredOwnerKeys(ISpotManagementCallbacks callbacks, in ImportRequiredOwnerKeysRequest request) = 2; + void syncOwnerKey(ISpotManagementCallbacks callbacks, in SyncOwnerKeyRequest request) = 3; + void importGivenOwnerKey(ISpotManagementCallbacks callbacks, in ImportGivenOwnerKeyRequest request) = 4; + void getFindMyDeviceSettings(ISpotManagementCallbacks callbacks, in GetFindMyDeviceSettingsRequest request) = 5; + void changeFindMyDeviceSettings(ISpotManagementCallbacks callbacks, in ChangeFindMyDeviceSettingsRequest request) = 6; + void getKeychainLockScreenKnowledgeFactorSupport(ISpotManagementCallbacks callbacks, in GetKeychainLockScreenKnowledgeFactorSupportRequest request) = 7; + void getCachedDevices(ISpotManagementCallbacks callbacks, in GetCachedDevicesRequest request) = 8; +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/CachedSpotDevice.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/CachedSpotDevice.java new file mode 100644 index 0000000000..f2058126c3 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/CachedSpotDevice.java @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class CachedSpotDevice extends AbstractSafeParcelable { + @Field(1) + public String deviceId; + + @Field(2) + public String bluetoothAddress; + + @Constructor + public CachedSpotDevice() { + + } + + @Constructor + public CachedSpotDevice(@Param(1) String deviceId, @Param(2) String bluetoothAddress) { + this.deviceId = deviceId; + this.bluetoothAddress = bluetoothAddress; + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(CachedSpotDevice.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("CachedSpotDevice") + .field("deviceId", deviceId) + .field("bluetoothAddress", bluetoothAddress) + .end(); + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsRequest.java new file mode 100644 index 0000000000..4fb1f0575d --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsRequest.java @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class ChangeFindMyDeviceSettingsRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ChangeFindMyDeviceSettingsRequest.class); + @Field(1) + public Boolean isEnabled; + @Field(4) + public Boolean isLocationReportingEnabled; + @Field(5) + public FindMyDeviceNetworkSettings networkSettings; + @Field(3) + public boolean isAsync; + + @Constructor + public ChangeFindMyDeviceSettingsRequest() { + + } + + @Constructor + public ChangeFindMyDeviceSettingsRequest(@Param(1) Boolean isEnabled, @Param(4) Boolean isLocationReportingEnabled, @Param(5) FindMyDeviceNetworkSettings networkSettings, @Param(3) boolean isAsync) { + this.isEnabled = isEnabled; + this.isLocationReportingEnabled = isLocationReportingEnabled; + this.networkSettings = networkSettings; + this.isAsync = isAsync; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("ChangeFindMyDeviceSettingsRequest").field("isEnabled", isEnabled).field("isLocationReportingEnabled", isLocationReportingEnabled).field("networkSettings", networkSettings).field("isAsync", isAsync).end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsResponse.java new file mode 100644 index 0000000000..54f1fb615b --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ChangeFindMyDeviceSettingsResponse.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class ChangeFindMyDeviceSettingsResponse extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ChangeFindMyDeviceSettingsResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/DisableLocationReportingRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/DisableLocationReportingRequest.java new file mode 100644 index 0000000000..47c081116b --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/DisableLocationReportingRequest.java @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class DisableLocationReportingRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(DisableLocationReportingRequest.class); + + @Field(1) + public String reason; + + @Constructor + public DisableLocationReportingRequest(@Param(1) String reason) { + this.reason = reason; + } + + @Constructor + public DisableLocationReportingRequest() { + + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("DisableLocationReportingRequest") + .field("reason", reason) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/DisableLocationReportingResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/DisableLocationReportingResponse.java new file mode 100644 index 0000000000..8d45cb9f28 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/DisableLocationReportingResponse.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class DisableLocationReportingResponse extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(DisableLocationReportingResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/FindMyDeviceNetworkSettings.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/FindMyDeviceNetworkSettings.java new file mode 100644 index 0000000000..0ec8b62c5c --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/FindMyDeviceNetworkSettings.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class FindMyDeviceNetworkSettings extends AbstractSafeParcelable { + @Field(1) + public int finderNetworkState; + + @Constructor + public FindMyDeviceNetworkSettings() { + + } + + @Constructor + public FindMyDeviceNetworkSettings(@Param(1) int finderNetworkState) { + this.finderNetworkState = finderNetworkState; + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(FindMyDeviceNetworkSettings.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetCachedDevicesRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetCachedDevicesRequest.java new file mode 100644 index 0000000000..6fa53bbcbe --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetCachedDevicesRequest.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class GetCachedDevicesRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetCachedDevicesRequest.class); + + @Field(1) + public Account account; + + @Constructor + public GetCachedDevicesRequest() { + + } + + @Constructor + public GetCachedDevicesRequest(@Param(1) Account account) { + this.account = account; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("GetCachedDevicesRequest") + .field("account", account) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetCachedDevicesResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetCachedDevicesResponse.java new file mode 100644 index 0000000000..b8ad7d8491 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetCachedDevicesResponse.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GetCachedDevicesResponse extends AbstractSafeParcelable { + @Field(1) + public CachedSpotDevice[] devices; + + @Constructor + public GetCachedDevicesResponse() { + + } + + @Constructor + public GetCachedDevicesResponse(@Param(1) CachedSpotDevice[] cachedSpotDeviceArr) { + this.devices = cachedSpotDeviceArr; + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetCachedDevicesResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsRequest.java new file mode 100644 index 0000000000..aded93b126 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsRequest.java @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class GetFindMyDeviceSettingsRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetFindMyDeviceSettingsRequest.class); + + @Field(1) + public boolean forceRefresh; + + @Constructor + public GetFindMyDeviceSettingsRequest() { + + } + + @Constructor + public GetFindMyDeviceSettingsRequest(@Param(1) boolean forceRefresh) { + this.forceRefresh = forceRefresh; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("GetFindMyDeviceSettingsRequest") + .field("forceRefresh", forceRefresh) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsResponse.java new file mode 100644 index 0000000000..ae8ce54d9c --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetFindMyDeviceSettingsResponse.java @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GetFindMyDeviceSettingsResponse extends AbstractSafeParcelable { + + @Field(5) + public long lastEnableAllLocationTime; + + @Field(6) + public boolean isFmdEnable; + + @Field(7) + public boolean isNetworkLocationEnabled; + + @Field(8) + public boolean isLklFullyEnabled; + + @Field(9) + public boolean hasOwnerKey; + + @Field(10) + public FindMyDeviceNetworkSettings networkSettings; + + @Field(11) + public long lastEnableHighTrafficTime; + + @Field(12) + public Account account; + + @Field(13) + public boolean isLocationReportingEnabled; + + @Field(14) + public int deviceStatus; + + @Field(15) + public Long unknownTime; + + @Field(16) + public FindMyDeviceNetworkSettings findMyDeviceNetworkSettings; + + @Field(17) + public boolean requiresUserConsent; + + @Constructor + public GetFindMyDeviceSettingsResponse() { + + } + + @Constructor + public GetFindMyDeviceSettingsResponse(@Param(5) long lastEnableAllLocationTime, @Param(6) boolean IsFmdEnable, @Param(7) boolean isNetworkLocationEnabled, + @Param(8) boolean isLklFullyEnabled, @Param(9) boolean hasOwnerKey, @Param(10) FindMyDeviceNetworkSettings networkSettings, + @Param(11) long lastEnableHigh, @Param(12) Account account, @Param(13) boolean isLocationReportingEnabled, + @Param(14) int deviceStatus, @Param(15) Long unknownTime, @Param(16) FindMyDeviceNetworkSettings findMyDeviceNetworkSettings, + @Param(17) boolean requiresUserConsent) { + this.lastEnableAllLocationTime = lastEnableAllLocationTime; + this.isFmdEnable = IsFmdEnable; + this.isNetworkLocationEnabled = isNetworkLocationEnabled; + this.isLklFullyEnabled = isLklFullyEnabled; + this.hasOwnerKey = hasOwnerKey; + this.networkSettings = networkSettings; + this.lastEnableHighTrafficTime = lastEnableHigh; + this.account = account; + this.isLocationReportingEnabled = isLocationReportingEnabled; + this.deviceStatus = deviceStatus; + this.unknownTime = unknownTime; + this.findMyDeviceNetworkSettings = findMyDeviceNetworkSettings; + this.requiresUserConsent = requiresUserConsent; + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetFindMyDeviceSettingsResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + @Override + public String toString() { + return "GetFindMyDeviceSettingsResponse{" + + "isEnabled=" + isFmdEnable + + ", isNetworkLocationEnabled=" + isNetworkLocationEnabled + + ", networkSettings=" + (networkSettings != null ? "mode=" + networkSettings.finderNetworkState : "null") + + ", lastUpdateTime=" + lastEnableAllLocationTime + + ", lastEnableHighTrafficTime=" + lastEnableHighTrafficTime + + ", isLklFullyEnabled=" + isLklFullyEnabled + + ", hasOwnerKey=" + hasOwnerKey + + ", account=" + (account != null ? account.name : "null") + + ", isLocationReportingEnabled=" + isLocationReportingEnabled + + ", deviceStatus=" + deviceStatus + + ", unknownTime=" + unknownTime + + ", findMyDeviceNetworkSettings=" + (findMyDeviceNetworkSettings != null ? "mode=" + findMyDeviceNetworkSettings.finderNetworkState : "null") + + ", requiresUserConsent=" + requiresUserConsent + + '}'; + } + +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportRequest.java new file mode 100644 index 0000000000..322fd7adac --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportRequest.java @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class GetKeychainLockScreenKnowledgeFactorSupportRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetKeychainLockScreenKnowledgeFactorSupportRequest.class); + + @Field(1) + public Account account; + @Field(2) + public boolean isSupport; + + @Constructor + public GetKeychainLockScreenKnowledgeFactorSupportRequest() { + + } + + @Constructor + public GetKeychainLockScreenKnowledgeFactorSupportRequest(@Param(1) Account account, @Param(2) boolean isSupport) { + this.account = account; + this.isSupport = isSupport; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("GetKeychainLockScreenKnowledgeFactorSupportRequest") + .field("account", account) + .field("isSupport", isSupport) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportResponse.java new file mode 100644 index 0000000000..b09cbc7e87 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetKeychainLockScreenKnowledgeFactorSupportResponse.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GetKeychainLockScreenKnowledgeFactorSupportResponse extends AbstractSafeParcelable { + @Field(1) + public boolean hasKeychainSupport; + + @Field(2) + public boolean hasLockScreenSupport; + + @Field(3) + public Boolean keychainStatus; + + @Field(4) + public Boolean lockScreenStatus; + + @Constructor + public GetKeychainLockScreenKnowledgeFactorSupportResponse() { + } + + @Constructor + public GetKeychainLockScreenKnowledgeFactorSupportResponse(@Param(1) boolean hasKeychainSupport, @Param(2) boolean hasLockScreenSupport, @Param(3) Boolean keychainStatus, @Param(4) Boolean lockScreenStatus) { + this.hasKeychainSupport = hasKeychainSupport; + this.hasLockScreenSupport = hasLockScreenSupport; + this.keychainStatus = keychainStatus; + this.lockScreenStatus = lockScreenStatus; + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetKeychainLockScreenKnowledgeFactorSupportResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetLocationReportingStateRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetLocationReportingStateRequest.java new file mode 100644 index 0000000000..142320a0f3 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetLocationReportingStateRequest.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GetLocationReportingStateRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetLocationReportingStateRequest.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetLocationReportingStateResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetLocationReportingStateResponse.java new file mode 100644 index 0000000000..e5f2bd04b2 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetLocationReportingStateResponse.java @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class GetLocationReportingStateResponse extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetLocationReportingStateResponse.class); + + @Field(1) + public boolean enabled; + + @Constructor + public GetLocationReportingStateResponse(@Param(1) boolean enabled) { + this.enabled = enabled; + } + + @Constructor + public GetLocationReportingStateResponse() { + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("GetLocationReportingStateResponse") + .field("enabled", enabled) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetOwnerKeyRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetOwnerKeyRequest.java new file mode 100644 index 0000000000..4d67da5207 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetOwnerKeyRequest.java @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class GetOwnerKeyRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetOwnerKeyRequest.class); + + @Field(1) + public Account account; + + @Constructor + public GetOwnerKeyRequest() { + + } + + @Constructor + public GetOwnerKeyRequest(@Param(1) Account account) { + this.account = account; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("GetOwnerKeyRequest") + .field("account", account) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetOwnerKeyResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetOwnerKeyResponse.java new file mode 100644 index 0000000000..e594d792f3 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/GetOwnerKeyResponse.java @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GetOwnerKeyResponse extends AbstractSafeParcelable { + + @Field(1) + public int keyType; + + @Field(2) + public byte[] keyData; + + @Constructor + public GetOwnerKeyResponse() { + } + + @Constructor + public GetOwnerKeyResponse(@Param(1) int keyType, @Param(2) byte[] keyData) { + this.keyType = keyType; + this.keyData = keyData; + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(GetOwnerKeyResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyRequest.java new file mode 100644 index 0000000000..e622e46045 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyRequest.java @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class ImportGivenOwnerKeyRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ImportGivenOwnerKeyRequest.class); + + @Field(1) + public Account account; + + @Field(2) + public int key; + + @Constructor + public ImportGivenOwnerKeyRequest() { + } + + @Constructor + public ImportGivenOwnerKeyRequest(@Param(1) Account account, @Param(2) int key) { + this.account = account; + this.key = key; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("ImportGivenOwnerKeyRequest") + .field("account", account) + .field("key", key) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyResponse.java new file mode 100644 index 0000000000..9890c1542d --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportGivenOwnerKeyResponse.java @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class ImportGivenOwnerKeyResponse extends AbstractSafeParcelable { + @Field(1) + public boolean success; + + @Constructor + public ImportGivenOwnerKeyResponse() { + } + + @Constructor + public ImportGivenOwnerKeyResponse(@Param(1) boolean success) { + this.success = success; + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ImportGivenOwnerKeyResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysRequest.java new file mode 100644 index 0000000000..372e9e1d42 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysRequest.java @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class ImportRequiredOwnerKeysRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ImportRequiredOwnerKeysRequest.class); + + @Field(1) + public Account account; + + @Constructor + public ImportRequiredOwnerKeysRequest() { + } + + @Constructor + public ImportRequiredOwnerKeysRequest(@Param(1) Account account) { + this.account = account; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("ImportRequiredOwnerKeysRequest") + .field("account", account) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysResponse.java new file mode 100644 index 0000000000..376b650ca1 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ImportRequiredOwnerKeysResponse.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class ImportRequiredOwnerKeysResponse extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ImportRequiredOwnerKeysResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/LocationReportRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/LocationReportRequest.java new file mode 100644 index 0000000000..da59d090fc --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/LocationReportRequest.java @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +import java.util.Arrays; + +@SafeParcelable.Class +public class LocationReportRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(LocationReportRequest.class); + + @Field(1) + public ScanResult[] scanResults; + + @Field(2) + public int type; + + @Constructor + public LocationReportRequest() { + } + + @Constructor + public LocationReportRequest(@Param(1) ScanResult[] scanResults, @Param(2) int type) { + this.scanResults = scanResults; + this.type = type; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("LocationReportRequest") + .field("scanResults", Arrays.toString(scanResults)) + .field("type", type) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/LocationReportResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/LocationReportResponse.java new file mode 100644 index 0000000000..f306f3d0bf --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/LocationReportResponse.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class LocationReportResponse extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(LocationReportResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/OwnersLocationReportRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/OwnersLocationReportRequest.java new file mode 100644 index 0000000000..2ea4f3564c --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/OwnersLocationReportRequest.java @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class OwnersLocationReportRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(OwnersLocationReportRequest.class); + + @Field(1) + public Account account; + @Field(2) + public ScanResult scanResult; + + @Constructor + public OwnersLocationReportRequest() { + } + + @Constructor + public OwnersLocationReportRequest(@Param(1) Account account, @Param(2) ScanResult scanResult) { + this.account = account; + this.scanResult = scanResult; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/OwnersLocationReportResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/OwnersLocationReportResponse.java new file mode 100644 index 0000000000..c44183cca2 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/OwnersLocationReportResponse.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class OwnersLocationReportResponse extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(OwnersLocationReportResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ScanResult.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ScanResult.java new file mode 100644 index 0000000000..fb181cbb75 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/ScanResult.java @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +import java.util.Arrays; + +@SafeParcelable.Class +public class ScanResult extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ScanResult.class); + + @Field(1) + public byte[] address; + + @Field(2) + public int rssi; + + @Field(3) + public String name; + + @Field(4) + public long timestamp; + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("ScanResult") + .field("address", Arrays.toString(address)) + .field("rssi", rssi) + .field("name", name) + .field("timestamp", timestamp) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SetOwnerKeyRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SetOwnerKeyRequest.java new file mode 100644 index 0000000000..8a114cf75a --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SetOwnerKeyRequest.java @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class SetOwnerKeyRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SetOwnerKeyRequest.class); + + @Field(1) + public Account account; + + @Field(2) + public int keyType; + + @Field(3) + public byte[] keyData; + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("SetOwnerKeyRequest") + .field("account", account) + .field("keyType", keyType) + .field("keyData", keyData != null ? keyData.length + " bytes" : "null") + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SetOwnerKeyResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SetOwnerKeyResponse.java new file mode 100644 index 0000000000..86dc5f010d --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SetOwnerKeyResponse.java @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class SetOwnerKeyResponse extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SetOwnerKeyResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} + diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SyncOwnerKeyRequest.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SyncOwnerKeyRequest.java new file mode 100644 index 0000000000..7455371638 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SyncOwnerKeyRequest.java @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.accounts.Account; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import org.microg.gms.utils.ToStringHelper; + +@SafeParcelable.Class +public class SyncOwnerKeyRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SyncOwnerKeyRequest.class); + + @Field(1) + public Account account; + + @Constructor + public SyncOwnerKeyRequest() { + } + + @Constructor + public SyncOwnerKeyRequest(@Param(1) Account account) { + this.account = account; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("SyncOwnerKeyRequest") + .field("account", account) + .end(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} \ No newline at end of file diff --git a/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SyncOwnerKeyResponse.java b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SyncOwnerKeyResponse.java new file mode 100644 index 0000000000..1c584a5d8e --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/findmydevice/spot/SyncOwnerKeyResponse.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.findmydevice.spot; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class SyncOwnerKeyResponse extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SyncOwnerKeyResponse.class); + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt index 75b1e20c96..7a7e92387a 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/auth/AuthPrefs.kt @@ -40,4 +40,11 @@ object AuthPrefs { c.getInt(0) != 0 } } + + @JvmStatic + fun allowedFindDevices(context: Context): Boolean { + return SettingsContract.getSettings(context, Auth.getContentUri(context), arrayOf(Auth.FIND_DEVICES)) { c -> + c.getInt(0) != 0 + } + } } diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 5ef726412c..1aa500b970 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -164,6 +164,7 @@ object SettingsContract { const val INCLUDE_ANDROID_ID = "auth_include_android_id" const val STRIP_DEVICE_NAME = "auth_strip_device_name" const val TWO_STEP_VERIFICATION = "auth_two_step_verification" + const val FIND_DEVICES = "auth_allow_find_devices" val PROJECTION = arrayOf( TRUST_GOOGLE, @@ -171,6 +172,7 @@ object SettingsContract { INCLUDE_ANDROID_ID, STRIP_DEVICE_NAME, TWO_STEP_VERIFICATION, + FIND_DEVICES ) } diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index df0cabfd41..392a2c0e38 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -212,6 +212,7 @@ class SettingsProvider : ContentProvider() { Auth.INCLUDE_ANDROID_ID -> getSettingsBoolean(key, true) Auth.STRIP_DEVICE_NAME -> getSettingsBoolean(key, false) Auth.TWO_STEP_VERIFICATION -> getSettingsBoolean(key, false) + Auth.FIND_DEVICES -> getSettingsBoolean(key, false) else -> throw IllegalArgumentException("Unknown key: $key") } } @@ -226,6 +227,7 @@ class SettingsProvider : ContentProvider() { Auth.INCLUDE_ANDROID_ID -> editor.putBoolean(key, value as Boolean) Auth.STRIP_DEVICE_NAME -> editor.putBoolean(key, value as Boolean) Auth.TWO_STEP_VERIFICATION -> editor.putBoolean(key, value as Boolean) + Auth.FIND_DEVICES -> editor.putBoolean(key, value as Boolean) else -> throw IllegalArgumentException("Unknown key: $key") } } diff --git a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java index 6507424e8b..97b1364265 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java @@ -302,7 +302,7 @@ public enum GmsService { WALLET_OCR_INTERNAL(281, "com.google.android.gms.ocr.service.internal.START"), GSERVICES_API(282), FIND_MY_DEVICE(283), - FIND_MY_DEVICE_SPOT(284), + FIND_MY_DEVICE_SPOT(284, "com.google.android.gms.findmydevice.spot.service.management.START", "com.google.android.gms.findmydevice.spot.service.locationreport.START"), PO_TOKENS(285, "com.google.android.gms.potokens.service.START"), EXPOSURE_NOTIFICATION_PROMOS(286), FIDO_AUTHENTICATOR_SERVICE(287), diff --git a/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java b/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java index a9535e41bc..de352ec178 100644 --- a/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java +++ b/play-services-basement/src/main/java/org/microg/gms/gcm/GcmConstants.java @@ -29,6 +29,9 @@ public final class GcmConstants { public static final String ACTION_TASK_INITIALZE = "com.google.android.gms.gcm.SERVICE_ACTION_INITIALIZE"; public static final String ACTION_INSTANCE_ID = "com.google.android.gms.iid.InstanceID"; + public static final String EXTRA_GCM_TYPE = "com.google.android.gms.GCM_TYPE"; + public static final String EXTRA_GCM_PAYLOAD = "com.google.android.gms.GCM_PAYLOAD"; + public static final String EXTRA_GCM_RP = "rp"; public static final String EXTRA_APP = "app"; public static final String EXTRA_APP_OVERRIDE = "org.microg.gms.gcm.APP_OVERRIDE"; public static final String EXTRA_APP_ID = "appid"; diff --git a/play-services-core-proto/src/main/proto/fmd/spot.proto b/play-services-core-proto/src/main/proto/fmd/spot.proto new file mode 100644 index 0000000000..744b18ede7 --- /dev/null +++ b/play-services-core-proto/src/main/proto/fmd/spot.proto @@ -0,0 +1,129 @@ +syntax = "proto2"; + +package google.internal.fmd; + +option java_package = "org.microg.gms.fmd"; + +enum RemoteAction { + ACTION_UNKNOWN = 0; + NOOP = 1; + WIPE = 2; + LOCATE = 3; + RING = 4; + REMIND = 5; + LOCK = 6; + SITREP = 7; + AUTO_ENABLE_DEVICE_ADMIN = 8; + AUTO_DISABLE_DEVICE_ADMIN = 9; + ASSURE = 11; + TOS_PROMPT = 13; + RENAME = 14; + REMOVE_USER = 15; + QUICK_LOCK = 16; +} + +message RemotePolicy { + optional int32 action = 1; + optional string token = 2; + optional bytes email_hash = 3; + optional string new_password = 5; + optional string lock_message = 6; + optional bool enable_device_admin = 7; + optional bool include_battery_status = 8; + optional string phone_number = 9; + optional bool include_connectivity_status = 10; +} + +message Location { + optional double longitude = 1; + optional double latitude = 2; + optional float accuracy = 3; + optional int64 timestamp = 5; +} + +message PasswordRequirements { + optional int32 min_length = 1; + optional int32 min_letters = 2; + optional int32 min_lowercase = 3; + optional int32 min_uppercase = 4; + optional int32 min_numeric = 5; + optional int32 min_symbols = 6; + optional int32 min_non_letter = 7; + optional int32 max_failed_attempts = 8; + optional int32 password_quality = 9; +} + +message BatteryStatus { + optional int32 is_charging = 1; + optional int32 level = 2; + optional int32 scale = 3; + optional int32 status = 4; + optional int32 health = 5; + optional int32 plugged = 6; +} + +message ConnectivityStatus { + optional int32 is_connected = 3; + optional string type_name = 4; + optional int32 type = 5; + optional int32 subtype = 6; + optional string extra_info = 7; +} + +message RemotePayloadRequest { + optional Location location = 1; + optional string token = 2; + repeated int32 response_codes = 3; + optional PasswordRequirements password_requirements = 5; + optional BatteryStatus battery_status = 6; + optional ConnectivityStatus connectivity_status = 7; + optional bool has_lock_screen = 8; +} + +enum SitrepReason { + SITREP_REASON_UNKNOWN = 0; + ACCOUNTS_CHANGED = 1; + CONNECTIVITY_CHANGED = 2; + DEVICE_ADMIN_DISABLED = 3; + DEVICE_ADMIN_ENABLED = 4; + DEVICE_ADMIN_UNCHANGED_AFTER_PROMPT = 5; + GMS_UPDATED = 6; + GMS_GCM_REGISTERED = 7; + SITREP_REMOTE_INSTRUCTION = 8; + DEVICE_ADMIN_ALREADY_ENABLED = 9; + DEVICE_ADMIN_NOT_ENABLED = 10; + RETRY_AFTER_SERVER_DELAY = 11; + ASSURE_REMOTE_INSTRUCTION = 12; + LOCKSCREEN_STATE_CHANGED = 13; + PERIODIC_CHECK = 14; +} + +message DeviceAdminStatus { + optional bool is_device_admin = 1; + optional bool has_device_owner = 2; + optional bool has_profile_owner = 3; +} + +message SitrepRequest { + optional fixed64 android_id = 1; + optional int32 gms_version = 2; + optional string gcm_registration_id = 4; + optional DeviceAdminStatus device_admin_status = 5; + optional int32 reason = 6; + optional int32 retry_reason = 7; + optional int32 sdk_version = 8; + optional int32 phone_type = 9; + optional string device_data_version = 10; + optional bool lockscreen_enabled = 12; +} + +message SitrepResponse { +} + +message RemotePayloadResponse { +} + +service FmdApiService { + rpc ProcessSitrep(SitrepRequest) returns (SitrepResponse); + rpc RemotePayload(RemotePayloadRequest) returns (RemotePayloadResponse); +} \ No newline at end of file diff --git a/play-services-core/src/huawei/AndroidManifest.xml b/play-services-core/src/huawei/AndroidManifest.xml index 26aa9013ea..ed5b25087c 100644 --- a/play-services-core/src/huawei/AndroidManifest.xml +++ b/play-services-core/src/huawei/AndroidManifest.xml @@ -25,6 +25,9 @@ + diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 66038da7e2..553629d327 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -153,6 +153,8 @@ + + @@ -371,7 +373,6 @@ - @@ -778,6 +779,13 @@ + + + + + + + @@ -836,6 +844,14 @@ android:taskAffinity="org.microg.gms.settings"> + + + - - - - + @@ -1181,6 +1194,56 @@ android:name="com.google.android.gms.maps.auth.ApiTokenService" android:exported="true"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/java/org/microg/gms/ui/LocationSettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/LocationSettingsActivity.java index b3da114f1f..0ae8e0e06e 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/LocationSettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/LocationSettingsActivity.java @@ -16,7 +16,28 @@ package org.microg.gms.ui; +import static org.microg.gms.accountsettings.ui.ExtensionsKt.ACTION_LOCATION_SHARING; + import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import org.microg.gms.accountsettings.ui.MainActivity; public class LocationSettingsActivity extends Activity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + try { + if (ACTION_LOCATION_SHARING.equals(getIntent().getAction())) { + Intent intent = new Intent(this, MainActivity.class); + intent.setAction(ACTION_LOCATION_SHARING); + startActivity(intent); + } + } catch (Exception ignore) { + } + finish(); + } } diff --git a/play-services-core/src/main/kotlin/com/google/android/gms/locationsharingreporter/service/LocationSharingReporterExtensions.kt b/play-services-core/src/main/kotlin/com/google/android/gms/locationsharingreporter/service/LocationSharingReporterExtensions.kt index 7da2e1e72e..dd8f5664cb 100644 --- a/play-services-core/src/main/kotlin/com/google/android/gms/locationsharingreporter/service/LocationSharingReporterExtensions.kt +++ b/play-services-core/src/main/kotlin/com/google/android/gms/locationsharingreporter/service/LocationSharingReporterExtensions.kt @@ -232,7 +232,7 @@ fun getLocationReportingStatus(context: Context) : Pair, Map Switch not allowed ") + return + } + Log.d(TAG, "onReceive: action: ${intent.action}") + val callIntent = Intent(context, GcmReceiverService::class.java) + callIntent.action = intent.action + intent.extras?.let { callIntent.putExtras(it) } + ForegroundServiceContext(context).startService(callIntent) + } +} + +class GcmReceiverService : ReceiverService(TAG) { + + companion object { + private val accountNotificationMap = HashMap>>() + private val notificationIdGenerator = AtomicInteger(0) + } + + override fun onCreate() { + super.onCreate() + if (SDK_INT >= 26) { + val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH) + val notificationManager: NotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + } + + override fun allowed(): Boolean = AuthPrefs.shouldReceiveTwoStepVerification(this) + + override fun receiver(intent: Intent) { + Log.d(TAG, "receiver: run thread -> ${Thread.currentThread().name}") + Log.d(TAG, "receiver: action: ${intent.action} data: ${intent.extras}") + val accountManager = getSystemService(ACCOUNT_SERVICE) as AccountManager? ?: return run { Log.w(TAG, "receiver: accountManager is null") } + val data = intent.extras ?: return run { Log.w(TAG, "receiver: intent.extras is null") } + if (intent.action == ACTION_GCM_NOTIFY_COMPLETE) { + Log.d(TAG, "receiver: remove Notification") + val accountName = intent.getStringExtra(EXTRA_NOTIFICATION_ACCOUNT) ?: return + val notificationList = accountNotificationMap[accountName] ?: return + notificationList.forEach { + val notificationId = it.first + val notificationData = it.second + updateNotificationReadState(accountManager, accountName, notificationData, NOTIFICATION_STATUS_COMPLETE) + NotificationManagerCompat.from(this).cancel(notificationId) + Log.d(TAG, "Notification with $accountName updateNotificationReadState <$notificationId> to Completed.") + } + accountNotificationMap.remove(accountName) + return + } + Log.d(TAG, "notifyVerificationInfo: from: ${data.getString(GcmConstants.EXTRA_FROM)} data: $data") + val gcmBodyType = data.getString(GcmConstants.EXTRA_GCM_BODY) ?: return run { Log.w(TAG, "receiver: EXTRA_GCM_BODY is null!") } + if (GMS_GCM_NOTIFICATIONS != gcmBodyType) return run { Log.w(TAG, "receiver: gcmBodyType is not notifications") } + val payloadData = data.getString(GcmConstants.EXTRA_GMS_GNOTS_PAYLOAD) ?: return run { Log.w(TAG, "receiver: payloadData is null!") } + val notificationData = NotificationData.ADAPTER.decode(Base64.decode(payloadData, DEFAULT_FLAGS)) + Log.w(TAG, "notifyVerificationInfo: $notificationData") + if (notificationData.isActive == true) return run { Log.w(TAG, "receiver: notification isActive!") } + val account = notificationData.userInfo?.userId?.let { id -> + accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).find { + accountManager.getUserData(it, AuthConstants.GOOGLE_USER_ID) == id + } + } ?: return run { Log.w(TAG, "receiver: account not matched!") } + Log.d(TAG, "notifyVerificationInfo: account: ${account.name}") + val identifierResponse = runBlocking { + repeat(NOTIFICATION_REPEAT_NUM) { attempt -> + try { + val notificationInfo = requestNotificationInfo(accountManager, account, notificationData) + if (notificationInfo.notifications?.notificationDataList.isNullOrEmpty()) { + throw RuntimeException("Notification not found") + } + return@runBlocking notificationInfo + } catch (e: Exception) { + Log.w(TAG, "Attempt ${attempt + 1} failed: ${e.message}") + } + delay(NOTIFICATION_DELAY_TIME) + } + return@runBlocking null + } + Log.d(TAG, "notifyVerificationInfo: identifierResponse: $identifierResponse") + val notifications = identifierResponse?.notifications?.notificationDataList ?: return run { Log.w(TAG, "receiver: notifications is null!") } + notifications.forEachIndexed { index, it -> + Log.d(TAG, "notifyVerificationInfo: notifications: index:$index it: $it") + updateNotificationReadState(accountManager, account.name, it, NOTIFICATION_STATUS_READY) + sendNotification(account, notificationIdGenerator.incrementAndGet(), it) + updateNotificationReadState(accountManager, account.name, it, NOTIFICATION_STATUS_COMPLETE) + } + } + + private fun sendNotification(account: Account, notificationId: Int, notificationData: NotificationData) { + if (notificationData.isActive == true) return + val content = notificationData.content ?: return + val intentExtras = notificationData.intentActions?.primaryPayload?.extras ?: return + val intent = Intent(this, MainActivity::class.java).apply { + `package` = Constants.GMS_PACKAGE_NAME + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK + intentExtras.forEach { putExtra(it.key, it.value_) } + putExtra(KEY_NOTIFICATION_ID, notificationId) + } + val pendingIntent = PendingIntentCompat.getActivity(this, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT, false) + val builder = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(content.accountName) + .setContentText(content.description) + .setStyle(NotificationCompat.BigTextStyle().bigText(content.description)) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .setSmallIcon(R.drawable.ic_google_logo) + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == + PackageManager.PERMISSION_GRANTED + ) { + NotificationManagerCompat.from(this).notify(notificationId, builder.build()) + } + runCatching { runOnMainLooper { startActivity(intent) } } + accountNotificationMap.getOrPut(account.name) { mutableListOf() }.add(Pair(notificationId, notificationData)) + } + + private fun getCurrentLanguageTag(): String { + return runCatching { + if (SDK_INT >= 24) { + resources.configuration.locales[0].toLanguageTag() + } else { + val locale = resources.configuration.locale + locale.language + (if (locale.country.isEmpty()) "" else "-" + locale.country) + } + }.getOrDefault(Locale.getDefault().language) + } + + private fun getDensityQualifier(): DeviceInfo.DensityQualifier { + val dpi = resources.displayMetrics.densityDpi + return when { + dpi >= DisplayMetrics.DENSITY_XXHIGH -> DeviceInfo.DensityQualifier.XXHDPI + dpi >= DisplayMetrics.DENSITY_XHIGH -> DeviceInfo.DensityQualifier.XHDPI + dpi >= DisplayMetrics.DENSITY_HIGH -> DeviceInfo.DensityQualifier.HDPI + dpi >= DisplayMetrics.DENSITY_TV -> DeviceInfo.DensityQualifier.TVDPI + dpi >= DisplayMetrics.DENSITY_MEDIUM -> DeviceInfo.DensityQualifier.MDPI + else -> DeviceInfo.DensityQualifier.LDPI + } + } + + private fun requestNotificationInfo(accountManager: AccountManager, account: Account, notificationData: NotificationData) = + getGunsApiServiceClient(account, accountManager!!).GmsGnotsFetchByIdentifier().executeBlocking(FetchByIdentifierRequest.Builder().apply { + config(GmsConfig.Builder().apply { + versionInfo(GmsConfig.GmsVersionInfo(Constants.GMS_VERSION_CODE)) + }.build()) + identifiers(NotificationIdentifierList.Builder().apply { + deviceInfo(DeviceInfo.Builder().apply { + densityQualifier(getDensityQualifier()) + localeTag(getCurrentLanguageTag()) + sdkVersion(SDK_INT) + density(resources.displayMetrics.density) + timeZoneId(TimeZone.getDefault().id) + }.build()) + notifications(notificationData.identifier?.let { listOf(it) } ?: emptyList()) + }.build()) + }.build()) + + private fun updateNotificationReadState(accountManager: AccountManager, accountName: String, notificationData: NotificationData, readState: Int) { + if (accountName.isEmpty() || notificationData.identifier?.uniqueId?.isEmpty() == true) { + return + } + try { + val identifier = notificationData.identifier + val readStateList = when { + readState == NOTIFICATION_STATUS_COMPLETE -> { + listOf( + ReadStateItem.Builder().apply { + this.notification = identifier + this.state = null + this.status = readState + }.build() + ) + } + + notificationData.content?.actionButtons.isNullOrEmpty() -> { + Log.w(TAG, "No action buttons found, skipping read state update.") + return + } + + else -> { + notificationData.content!!.actionButtons.map { + ReadStateItem.Builder().apply { + this.notification = identifier + this.state = it.icon + this.status = readState + }.build() + } + } + } + sendNotificationReadState(accountManager, accountName, ReadStateList.Builder().apply { items = readStateList }.build()) + Log.i(TAG, "Notification read state updated successfully for account: $accountName") + } catch (e: Exception) { + Log.w(TAG, "Failed to update the notification(s) read state.", e) + } + } + + private fun sendNotificationReadState(accountManager: AccountManager, accountName: String, readStateList: ReadStateList) { + val account = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).find { it.name == accountName } ?: return + getGunsApiServiceClient(account, accountManager).GmsGnotsSetReadStates().executeBlocking( + GmsGnotsSetReadStatesRequest.Builder().apply { + config = GmsConfig.Builder().apply { + versionInfo(GmsConfig.GmsVersionInfo(Constants.GMS_VERSION_CODE)) + }.build() + readStates = readStateList + }.build() + ) + } + + private fun getGunsApiServiceClient(account: Account, accountManager: AccountManager): GunsGmscoreApiServiceClient { + val oauthToken = accountManager.blockingGetAuthToken(account, GMS_NOTS_OAUTH_SERVICE, true) + return createGrpcClient(baseUrl = GMS_NOTS_BASE_URL, interceptor = AuthHeaderInterceptor(oauthToken)) + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/AlarmRingService.kt b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/AlarmRingService.kt new file mode 100644 index 0000000000..201bc6c13f --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/AlarmRingService.kt @@ -0,0 +1,113 @@ +/** + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.findmydevice + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.media.AudioAttributes +import android.media.Ringtone +import android.media.RingtoneManager +import android.os.IBinder +import android.os.PowerManager +import org.microg.gms.profile.Build + +class AlarmRingService : Service() { + + private var ringtone: Ringtone? = null + private var wakeLock: PowerManager.WakeLock? = null + + override fun onCreate() { + super.onCreate() + acquireWakeLock() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.action) { + ACTION_START -> startRing() + ACTION_STOP -> stopRing() + } + return START_STICKY + } + + private fun startRing() { + if (ringtone?.isPlaying == true) return + + val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM) + + ringtone = RingtoneManager.getRingtone(this, uri).apply { + if (Build.VERSION.SDK_INT >= 21) { + audioAttributes = AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build() + } + if (Build.VERSION.SDK_INT >= 28) { + isLooping = true + } + play() + } + } + + private fun stopRing() { + ringtone?.let { + if (it.isPlaying) it.stop() + } + ringtone = null + releaseWakeLock() + if (Build.VERSION.SDK_INT >= 24) { + stopForeground(STOP_FOREGROUND_REMOVE) + } + stopSelf() + } + + private fun acquireWakeLock() { + val pm = getSystemService(POWER_SERVICE) as PowerManager + wakeLock = pm.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + "AlarmRingService::WakeLock" + ).apply { + acquire(10 * 60 * 1000L) + } + } + + private fun releaseWakeLock() { + wakeLock?.let { + if (it.isHeld) it.release() + } + wakeLock = null + } + + override fun onDestroy() { + stopRing() + super.onDestroy() + } + + override fun onBind(intent: Intent?): IBinder? = null + + companion object { + private const val ACTION_START = "alarm_ring_start" + private const val ACTION_STOP = "alarm_ring_stop" + + fun stopRing(context: Context) { + val intent = Intent(context, AlarmRingService::class.java).apply { + action = ACTION_STOP + } + context.startService(intent) + } + + fun startRing(context: Context) { + val intent = Intent(context, AlarmRingService::class.java).apply { + action = ACTION_START + } + if (Build.VERSION.SDK_INT >= 26) { + context.startForegroundService(intent) + } else { + context.startService(intent) + } + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/extensions.kt new file mode 100644 index 0000000000..d5d7a4cde7 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/extensions.kt @@ -0,0 +1,67 @@ +/** + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.findmydevice + +import androidx.annotation.RequiresApi +import com.google.android.gms.common.Feature +import okhttp3.Interceptor +import okhttp3.Response +import org.microg.gms.common.Constants +import org.microg.gms.profile.Build +import java.util.Locale +import kotlin.text.ifEmpty + +const val TAG = "GmsFindDevice" + +const val FIND_DEVICE_REMOTE_POLICY = "REMOTE_POLICY" + +const val GMS_FMD_OAUTH_SERVICE = "oauth2:https://www.googleapis.com/auth/android_device_manager" +const val FMD_BASE_URL = "https://findmydevice-pa.googleapis.com" +const val FMD_API_KEY = "AIzaSyAP-gfH3qvi6vgHZbSYwQ_XHqV_mXHhzIk" + +val FEATURES = arrayOf( + Feature("SPOT_MANAGEMENT", 10L), + Feature("SPOT_MANAGEMENT_CACHED_DEVICES", 1L), + Feature("SPOT_FAST_PAIR", 5L), + Feature("SPOT_FAST_PAIR_HISTORICAL_ACCOUNT_KEYS", 1L), + Feature("SPOT_LOCATION_REPORT", 10L), + Feature("SPOT_LOCATION_REPORT_DISABLE", 1L) +) + +class RemotePayloadInterceptor() : Interceptor { + @RequiresApi(21) + override fun intercept(chain: Interceptor.Chain): Response { + val requestBuilder = chain.request().newBuilder() + .header("user-agent", buildUserAgent()) + .header("x-goog-api-key", FMD_API_KEY) + .header("x-android-package", Constants.GMS_PACKAGE_NAME) + .header("x-android-cert", Constants.GMS_PACKAGE_SIGNATURE_SHA1) + return chain.proceed(requestBuilder.build()) + } +} + +class ProcessSitrepInterceptor( + private val authToken: String, + private val authTime: Long = System.currentTimeMillis() +) : Interceptor { + @RequiresApi(21) + override fun intercept(chain: Interceptor.Chain): Response { + val original = chain.request().newBuilder() + .header("user-agent", buildUserAgent()) + .header("authorization", "Bearer $authToken") + .header("x-auth-time", authTime.toString()) + return chain.proceed(original.build()) + } +} + +@RequiresApi(21) +private fun buildUserAgent(): String { + val locale = Locale.getDefault() + val localeStr = "${locale.language}_${locale.country}_#${locale.script.ifEmpty { "Hans" }}" + return "${Constants.GMS_PACKAGE_NAME}/${Constants.GMS_VERSION_CODE} " + + "(Linux; U; Android ${Build.VERSION.RELEASE}; $localeStr; ${Build.MODEL}; " + + "Build/${Build.ID}; Cronet/140.0.7289.0) grpc-java-cronet/1.76.0-SNAPSHOT" +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/gcm/GcmReceiverService.kt b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/gcm/GcmReceiverService.kt new file mode 100644 index 0000000000..804cd33c4d --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/gcm/GcmReceiverService.kt @@ -0,0 +1,325 @@ +/** + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.findmydevice.gcm + +import android.accounts.Account +import android.accounts.AccountManager +import android.app.KeyguardManager +import android.app.admin.DevicePolicyManager +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.net.wifi.WifiManager +import android.os.BatteryManager +import android.telephony.TelephonyManager +import android.util.Base64 +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.legacy.content.WakefulBroadcastReceiver +import com.google.android.gms.location.LocationServices +import com.google.android.gms.tasks.Tasks +import kotlinx.coroutines.runBlocking +import org.microg.gms.auth.AuthConstants +import org.microg.gms.auth.AuthManager +import org.microg.gms.auth.AuthPrefs +import org.microg.gms.checkin.LastCheckinInfo +import org.microg.gms.common.Constants +import org.microg.gms.common.ForegroundServiceContext +import org.microg.gms.findmydevice.AlarmRingService +import org.microg.gms.findmydevice.FIND_DEVICE_REMOTE_POLICY +import org.microg.gms.findmydevice.FMD_BASE_URL +import org.microg.gms.findmydevice.GMS_FMD_OAUTH_SERVICE +import org.microg.gms.findmydevice.ProcessSitrepInterceptor +import org.microg.gms.findmydevice.RemotePayloadInterceptor +import org.microg.gms.findmydevice.TAG +import org.microg.gms.fmd.BatteryStatus +import org.microg.gms.fmd.ConnectivityStatus +import org.microg.gms.fmd.DeviceAdminStatus +import org.microg.gms.fmd.FmdApiServiceClient +import org.microg.gms.fmd.Location +import org.microg.gms.fmd.PasswordRequirements +import org.microg.gms.fmd.RemotePayloadRequest +import org.microg.gms.fmd.RemotePolicy +import org.microg.gms.fmd.SitrepRequest +import org.microg.gms.gcm.GcmConstants +import org.microg.gms.gcm.KEY_GCM_REG_ID +import org.microg.gms.gcm.ReceiverService +import org.microg.gms.gcm.createGrpcClient +import org.microg.gms.profile.Build +import java.security.MessageDigest +import java.util.concurrent.TimeUnit + +class GcmReceiver : WakefulBroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val allowedFindDevices = AuthPrefs.allowedFindDevices(context) + if (!allowedFindDevices) { + Log.d(TAG, "onReceive: Switch not allowed ") + return + } + Log.d(TAG, "onReceive: action: ${intent.action}") + val callIntent = Intent(context, GcmReceiverService::class.java) + callIntent.action = intent.action + intent.extras?.let { callIntent.putExtras(it) } + ForegroundServiceContext(context).startService(callIntent) + } +} + +class GcmReceiverService : ReceiverService(TAG) { + + private enum class PolicyAction(val action: Int) { + ACTION_RESET(1), ACTION_REPORT(2), ACTION_RING_START(3), ACTION_LOCK(5), ACTION_FIND(6), ACTION_RING_STOP(10) + } + + override fun allowed(): Boolean = AuthPrefs.allowedFindDevices(this) + + override fun receiver(intent: Intent) { + Log.d(TAG, "receiver: run thread -> ${Thread.currentThread().name}") + Log.d(TAG, "receiver: action: ${intent.action} data: ${intent.extras}") + val data = intent.extras ?: return run { Log.w(TAG, "receiver: intent.extras is null") } + val accountManager = getSystemService(ACCOUNT_SERVICE) as AccountManager? ?: return run { Log.w(TAG, "receiver: accountManager is null") } + val gcmType = data.getString(GcmConstants.EXTRA_GCM_TYPE) + if (gcmType == null || gcmType != FIND_DEVICE_REMOTE_POLICY) { + Log.w(TAG, "receiver: gcmType not matched, type: $gcmType") + return + } + val remotePolicyData = data.getString(GcmConstants.EXTRA_GCM_RP) + val gcmPayload = data.getString(GcmConstants.EXTRA_GCM_PAYLOAD) + val policyData = if (!remotePolicyData.isNullOrEmpty()) remotePolicyData else gcmPayload + if (policyData.isNullOrEmpty()) { + Log.w(TAG, "No policy data available") + return + } + val remotePolicy = runCatching { + Base64.decode(policyData, Base64.DEFAULT)?.let { RemotePolicy.ADAPTER.decode(it) } + }.getOrNull() ?: return run { Log.w(TAG, "Invalid policy data") } + Log.d(TAG, "RemotePolicy parsed successfully: $remotePolicy") + val account = remotePolicy.email_hash?.let { emailHash -> + accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).find { + val digest = MessageDigest.getInstance("SHA-256").digest(it.name.lowercase().toByteArray(Charsets.UTF_8)) + digest.joinToString("") { c -> "%02x".format(c) } == emailHash.hex() + } + } ?: return run { Log.w(TAG, "receiver: account not matched!") } + Log.d(TAG, "receiver: account: ${account.name}") + handleRemotePolicyAction(remotePolicy, account) + } + + private fun handleRemotePolicyAction(policy: RemotePolicy, account: Account) { + Log.d(TAG, "handleRemotePolicyAction: action=${policy.action}, account=${account.name}") + val policyToken = policy.token ?: return run { Log.w(TAG, "handleRemotePolicyAction: policyToken is null!") } + Log.d(TAG, "handleRemotePolicyAction: policyToken: $policyToken") + when (policy.action) { + PolicyAction.ACTION_RESET.action -> { + Log.d(TAG, "handleRemotePolicyAction: Reset device.") + } + + PolicyAction.ACTION_REPORT.action -> { + Log.d(TAG, "handleRemotePolicyAction: Report device information.") + uploadRemotePayload( + policyToken, + uploadLocation = true, + uploadRequirements = true, + uploadBatteryInfo = policy.include_battery_status == true, + uploadConnectionInfo = policy.include_connectivity_status == true + ) + } + + PolicyAction.ACTION_RING_START.action -> { + Log.d(TAG, "handleRemotePolicyAction: Start ring device.") + uploadRemotePayload( + policyToken, + uploadBatteryInfo = policy.include_battery_status == true, + uploadConnectionInfo = policy.include_connectivity_status == true + ) + AlarmRingService.startRing(this) + } + + PolicyAction.ACTION_LOCK.action -> { + Log.d(TAG, "handleRemotePolicyAction: Lock device.") + } + + PolicyAction.ACTION_FIND.action -> { + Log.d(TAG, "handleRemotePolicyAction: Find device.") + uploadRemotePayload( + policyToken, + uploadBatteryInfo = policy.include_battery_status == true, + uploadConnectionInfo = policy.include_connectivity_status == true + ) + uploadProcessSitrep(account) + } + + PolicyAction.ACTION_RING_STOP.action -> { + Log.d(TAG, "handleRemotePolicyAction: Stop ring device.") + uploadRemotePayload( + policyToken, + uploadBatteryInfo = policy.include_battery_status == true, + uploadConnectionInfo = policy.include_connectivity_status == true + ) + AlarmRingService.stopRing(this) + } + + else -> { + Log.d(TAG, "handleRemotePolicyAction: Unknown action: ${policy.action}") + } + } + } + + private fun uploadRemotePayload( + policyToken: String, + uploadLocation: Boolean = false, + uploadBatteryInfo: Boolean = false, + uploadConnectionInfo: Boolean = false, + uploadRequirements: Boolean = false, + ) { + fun loadLocation(): Location { + val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) + val location = Tasks.await(fusedLocationClient.lastLocation, 5, TimeUnit.SECONDS) + return Location(latitude = location.latitude, longitude = location.longitude, accuracy = location.accuracy, timestamp = location.time) + } + + fun loadBatteryInfo(): BatteryStatus { + val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + val charging = if (batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0) 1 else 0 + val level = batteryIntent?.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) ?: 0 + val scale = batteryIntent?.getIntExtra(BatteryManager.EXTRA_SCALE, 100) ?: 100 + val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN) ?: BatteryManager.BATTERY_STATUS_UNKNOWN + val health = batteryIntent?.getIntExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN) ?: BatteryManager.BATTERY_HEALTH_UNKNOWN + val plugged = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) ?: 0 + return BatteryStatus(charging, level, scale, status, health, plugged) + } + + @RequiresApi(23) + fun loadConnectionInfo(): ConnectivityStatus { + val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + val wifiManager = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager + val telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager + val activeNetwork = connectivityManager.activeNetwork + val capabilities = activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) } + val isWifi = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true + val isMobile = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true + val signalLevel = if (isWifi) { + (WifiManager.calculateSignalLevel(wifiManager.connectionInfo.rssi, 4) + 1).coerceIn(1, 4) + } else if (isMobile && Build.VERSION.SDK_INT >= 28) { + telephonyManager.signalStrength?.let { (it.level + 1).coerceIn(1, 4) } ?: 2 + } else 2 + val carrierName = telephonyManager.simOperatorName.takeIf { it.isNotEmpty() } ?: "unknown" + val networkName = if (isWifi) { + wifiManager.connectionInfo.ssid?.takeIf { it != "" }?.replace("\"", "") ?: "WiFi" + } else "" + return ConnectivityStatus( + is_connected = if (isWifi) 1 else if (isMobile) 2 else 0, + type_name = networkName, + type = signalLevel, + subtype = signalLevel, + extra_info = carrierName + ) + } + + fun loadPasswordRequirements(): PasswordRequirements { + return PasswordRequirements( + min_length = 0, min_letters = 16, min_lowercase = 0, min_uppercase = 0, min_numeric = 0, + min_symbols = 0, min_non_letter = 0, max_failed_attempts = 0, password_quality = 0 + ) + } + + fun isDeviceLocked(): Boolean { + val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager + return keyguardManager.isKeyguardLocked + } + + val api = createGrpcClient( + baseUrl = FMD_BASE_URL, interceptor = RemotePayloadInterceptor() + ) + api.RemotePayload().executeBlocking( + RemotePayloadRequest( + token = policyToken, + response_codes = listOf(0), + location = if (uploadLocation) loadLocation() else null, + battery_status = if (uploadBatteryInfo) loadBatteryInfo() else null, + connectivity_status = if (uploadConnectionInfo) loadConnectionInfo() else null, + password_requirements = if (uploadRequirements) loadPasswordRequirements() else null, + has_lock_screen = isDeviceLocked() + ) + ) + } + + fun uploadProcessSitrep(account: Account) { + val regId = getSharedPreferences("com.google.android.gcm", MODE_PRIVATE)?.getString(KEY_GCM_REG_ID, null) + if (regId == null) { + Log.d(TAG, "uploadProcessSitrep: regId is null!") + return + } + Log.d(TAG, "uploadProcessSitrep: regId: $regId") + val androidId = LastCheckinInfo.read(this).androidId + if (androidId == 0L) { + Log.w(TAG, "Device not checked in, cannot upload!") + return + } + val deviceDataVersionInfo = LastCheckinInfo.read(this).deviceDataVersionInfo + Log.d(TAG, "uploadProcessSitrep: deviceDataVersion=$deviceDataVersionInfo") + val oauthToken = runBlocking { + val authManager = AuthManager(this@GcmReceiverService, account.name, Constants.GMS_PACKAGE_NAME, GMS_FMD_OAUTH_SERVICE) + runCatching { authManager.requestAuth(true).auth }.getOrNull() + } + if (oauthToken.isNullOrEmpty()) { + Log.w(TAG, "oauthToken get error!") + return + } + + fun isLockscreenEnabled(): Boolean { + val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager + return if (Build.VERSION.SDK_INT >= 23) { + keyguardManager.isDeviceSecure + } else { + @Suppress("DEPRECATION") + keyguardManager.isKeyguardSecure + } + } + + fun getPhoneType(): Int { + val telephonyManager = getSystemService(TELEPHONY_SERVICE) as? TelephonyManager + return telephonyManager?.phoneType ?: TelephonyManager.PHONE_TYPE_NONE + } + + fun getDeviceAdminStatus(): DeviceAdminStatus { + val devicePolicyManager = getSystemService(DEVICE_POLICY_SERVICE) as DevicePolicyManager + val hasDeviceOwner = if (Build.VERSION.SDK_INT >= 21) { + devicePolicyManager.isDeviceOwnerApp(packageName) + } else { + false + } + val hasProfileOwner = if (Build.VERSION.SDK_INT >= 21) { + devicePolicyManager.isProfileOwnerApp(packageName) + } else { + false + } + return DeviceAdminStatus( + is_device_admin = false, + has_device_owner = hasDeviceOwner, + has_profile_owner = hasProfileOwner + ) + } + + val api = createGrpcClient( + baseUrl = FMD_BASE_URL, interceptor = ProcessSitrepInterceptor(oauthToken) + ) + api.ProcessSitrep().executeBlocking( + SitrepRequest( + android_id = androidId, + gcm_registration_id = regId, + gms_version = Constants.GMS_VERSION_CODE, + reason = 8, + retry_reason = 0, + sdk_version = Build.VERSION.SDK_INT, + phone_type = getPhoneType(), + device_data_version = deviceDataVersionInfo, + lockscreen_enabled = isLockscreenEnabled(), + device_admin_status = getDeviceAdminStatus() + ) + ) + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/spot/locationreporting/LocationReportService.kt b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/spot/locationreporting/LocationReportService.kt new file mode 100644 index 0000000000..058c1a0af6 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/spot/locationreporting/LocationReportService.kt @@ -0,0 +1,61 @@ +/** + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.findmydevice.spot.locationreporting + +import android.content.Context +import android.os.Parcel +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +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.findmydevice.spot.DisableLocationReportingRequest +import com.google.android.gms.findmydevice.spot.GetLocationReportingStateRequest +import com.google.android.gms.findmydevice.spot.LocationReportRequest +import com.google.android.gms.findmydevice.spot.internal.ISpotLocationReportCallbacks +import com.google.android.gms.findmydevice.spot.internal.ISpotLocationReportService +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils +import org.microg.gms.findmydevice.FEATURES +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "LocationReportService" + +class LocationReportService : BaseService(TAG, GmsService.FIND_MY_DEVICE_SPOT) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) + ?: throw IllegalArgumentException("Missing package name") + val spotManagementServiceImpl = SpotLocationReportServiceImpl(packageName, this, lifecycle) + callback.onPostInitCompleteWithConnectionInfo(0, spotManagementServiceImpl.asBinder(), ConnectionInfo().apply { + features = FEATURES + }) + } +} + +class SpotLocationReportServiceImpl(val packageName: String, val context: Context, override val lifecycle: Lifecycle) : ISpotLocationReportService.Stub(), LifecycleOwner { + override fun locationReport(callbacks: ISpotLocationReportCallbacks?, request: LocationReportRequest?) { + Log.d(TAG, "Unimplement locationReport: $request") + } + + override fun getLocationReportingState( + callbacks: ISpotLocationReportCallbacks?, + request: GetLocationReportingStateRequest? + ) { + Log.d(TAG, "Unimplement getLocationReportingState: $request") + } + + override fun disableLocationReporting( + callbacks: ISpotLocationReportCallbacks?, + request: DisableLocationReportingRequest? + ) { + Log.d(TAG, "Unimplement disableLocationReporting: $request") + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/spot/management/ManagementService.kt b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/spot/management/ManagementService.kt new file mode 100644 index 0000000000..4529ba7ce1 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/findmydevice/spot/management/ManagementService.kt @@ -0,0 +1,111 @@ +/** + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.findmydevice.spot.management + +import android.content.Context +import android.os.Parcel +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.internal.ConnectionInfo +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.findmydevice.spot.CachedSpotDevice +import com.google.android.gms.findmydevice.spot.ChangeFindMyDeviceSettingsRequest +import com.google.android.gms.findmydevice.spot.GetCachedDevicesRequest +import com.google.android.gms.findmydevice.spot.GetCachedDevicesResponse +import com.google.android.gms.findmydevice.spot.GetFindMyDeviceSettingsRequest +import com.google.android.gms.findmydevice.spot.GetFindMyDeviceSettingsResponse +import com.google.android.gms.findmydevice.spot.GetKeychainLockScreenKnowledgeFactorSupportRequest +import com.google.android.gms.findmydevice.spot.GetOwnerKeyRequest +import com.google.android.gms.findmydevice.spot.ImportGivenOwnerKeyRequest +import com.google.android.gms.findmydevice.spot.ImportRequiredOwnerKeysRequest +import com.google.android.gms.findmydevice.spot.SetOwnerKeyRequest +import com.google.android.gms.findmydevice.spot.SyncOwnerKeyRequest +import com.google.android.gms.findmydevice.spot.internal.ISpotManagementCallbacks +import com.google.android.gms.findmydevice.spot.internal.ISpotManagementService +import org.microg.gms.BaseService +import org.microg.gms.auth.AuthPrefs +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils +import org.microg.gms.findmydevice.FEATURES +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "ManagementService" + +class ManagementService : BaseService(TAG, GmsService.FIND_MY_DEVICE_SPOT) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) + ?: throw IllegalArgumentException("Missing package name") + val spotManagementServiceImpl = SpotManagementServiceImpl(packageName, this, lifecycle) + callback.onPostInitCompleteWithConnectionInfo(0, spotManagementServiceImpl.asBinder(), ConnectionInfo().apply { + features = FEATURES + }) + } +} + +class SpotManagementServiceImpl(val packageName: String, val context: Context, override val lifecycle: Lifecycle) : ISpotManagementService.Stub(), LifecycleOwner { + + override fun getOwnerKey(callbacks: ISpotManagementCallbacks?, request: GetOwnerKeyRequest?) { + Log.d(TAG, "Unimplement getOwnerKey: $request") + } + + override fun setOwnerKey(callbacks: ISpotManagementCallbacks?, request: SetOwnerKeyRequest?) { + Log.d(TAG, "Unimplement setOwnerKey: $request") + } + + override fun importRequiredOwnerKeys( + callbacks: ISpotManagementCallbacks?, + request: ImportRequiredOwnerKeysRequest? + ) { + Log.d(TAG, "Unimplement importRequiredOwnerKeys: $request") + } + + override fun syncOwnerKey(callbacks: ISpotManagementCallbacks?, request: SyncOwnerKeyRequest?) { + Log.d(TAG, "Unimplement syncOwnerKey: $request") + } + + override fun importGivenOwnerKey( + callbacks: ISpotManagementCallbacks?, + request: ImportGivenOwnerKeyRequest? + ) { + Log.d(TAG, "Unimplement importGivenOwnerKey: $request") + } + + override fun getFindMyDeviceSettings( + callbacks: ISpotManagementCallbacks?, + request: GetFindMyDeviceSettingsRequest? + ) { + Log.d(TAG, "Unimplement getFindMyDeviceSettings: $request") + callbacks?.onGetFindMyDeviceSettings(Status.SUCCESS, GetFindMyDeviceSettingsResponse().apply { + isFmdEnable = AuthPrefs.allowedFindDevices(context) + deviceStatus = 1 + }) + } + + override fun changeFindMyDeviceSettings( + callbacks: ISpotManagementCallbacks?, + request: ChangeFindMyDeviceSettingsRequest? + ) { + Log.d(TAG, "Unimplement changeFindMyDeviceSettings: $request") + } + + override fun getKeychainLockScreenKnowledgeFactorSupport( + callbacks: ISpotManagementCallbacks?, + request: GetKeychainLockScreenKnowledgeFactorSupportRequest? + ) { + Log.d(TAG, "Unimplement getKeychainLockScreenKnowledgeFactorSupport: $request") + } + + override fun getCachedDevices(callbacks: ISpotManagementCallbacks?, request: GetCachedDevicesRequest?) { + Log.d(TAG, "Unimplement getCachedDevices: $request") + callbacks?.onGetCachedDevices(Status.SUCCESS, GetCachedDevicesResponse(emptyArray())) + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt index 36b0c36994..176644145b 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt @@ -5,16 +5,11 @@ package org.microg.gms.gcm -import android.Manifest import android.accounts.Account import android.accounts.AccountManager -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.SharedPreferences -import android.content.pm.PackageManager import android.os.Binder import android.os.Bundle import android.os.Handler @@ -22,53 +17,25 @@ import android.os.Looper import android.os.Message import android.os.Messenger import android.os.Process -import android.util.Base64 -import android.util.DisplayMetrics import android.util.Log -import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import androidx.core.app.PendingIntentCompat import androidx.legacy.content.WakefulBroadcastReceiver import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import com.google.android.gms.BuildConfig -import com.google.android.gms.R import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -import okio.ByteString -import org.microg.gms.accountsettings.ui.KEY_NOTIFICATION_ID -import org.microg.gms.accountsettings.ui.MainActivity import org.microg.gms.auth.AuthConstants import org.microg.gms.auth.AuthManager import org.microg.gms.auth.AuthPrefs -import org.microg.gms.auth.AuthResponse -import org.microg.gms.auth.ItAuthData -import org.microg.gms.auth.ItMetadataData -import org.microg.gms.auth.OAuthAuthorization -import org.microg.gms.auth.OAuthTokenData -import org.microg.gms.auth.TokenField import org.microg.gms.checkin.LastCheckinInfo import org.microg.gms.common.Constants import org.microg.gms.common.ForegroundServiceContext import org.microg.gms.gcm.registeration.ChimeGmsRegistrationHelper -import org.microg.gms.profile.Build.VERSION.SDK_INT import org.microg.gms.profile.ProfileManager -import java.util.Locale -import java.util.TimeZone -import java.util.concurrent.atomic.AtomicInteger -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine -import kotlin.math.min private const val TAG = "GcmInGmsService" -private const val KEY_GCM_ANDROID_ID = "androidId" -private const val KEY_GCM_REG_ID = "regId" private const val KEY_GCM_REG_SENDER = "sender" private const val KEY_GCM_REG_TIME = "reg_time" private const val KEY_GCM_REG_ACCOUNT_LIST = "accountList" @@ -79,24 +46,9 @@ private const val GMS_GCM_REGISTER_SUBTYPE = "745476177629" private const val GMS_GCM_REGISTER_SUBSCRIPTION = "745476177629" private const val GCM_GROUP_SENDER = "google.com" private const val GCM_GMS_REG_REFRESH_S = 604800L - -private const val DEFAULT_FLAGS = Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING -private const val AUTHS_TOKEN_PREFIX = "ya29.m." private const val GMS_GCM_OAUTH_SERVICE = "oauth2:https://www.googleapis.com/auth/gcm" -private const val CHANNEL_ID = "gcm_notification" -private const val CHANNEL_NAME = "gnots" -private const val GMS_GCM_NOTIFICATIONS = "notifications" -private const val NOTIFICATION_STATUS_READY = 2 -private const val NOTIFICATION_STATUS_COMPLETE = 5 -private const val NOTIFICATION_REPEAT_NUM = 3 -private const val NOTIFICATION_DELAY_TIME = 500L - class GcmInGmsService : LifecycleService() { - companion object { - private val accountNotificationMap = HashMap>>() - private val notificationIdGenerator = AtomicInteger(0) - } private var sp: SharedPreferences? = null private var accountManager: AccountManager? = null private val chimeGmsRegistrationHelper by lazy { ChimeGmsRegistrationHelper(this) } @@ -106,11 +58,6 @@ class GcmInGmsService : LifecycleService() { ProfileManager.ensureInitialized(this) sp = getSharedPreferences("com.google.android.gcm", MODE_PRIVATE) ?: throw RuntimeException("sp get error") accountManager = getSystemService(ACCOUNT_SERVICE) as AccountManager? ?: throw RuntimeException("accountManager is null") - if (SDK_INT >= 26) { - val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH) - val notificationManager: NotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - notificationManager.createNotificationChannel(channel) - } } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -153,7 +100,10 @@ class GcmInGmsService : LifecycleService() { when (action) { GcmConstants.ACTION_C2DM_RECEIVE -> { Log.d(TAG, "start handle gcm message") - intent.extras?.let { notifyVerificationInfo(it) } + val callerIntent = Intent(ACTION_GCM_MESSAGE_RECEIVE) + callerIntent.setPackage(Constants.GMS_PACKAGE_NAME) + callerIntent.putExtras(intent) + sendOrderedBroadcast(callerIntent, null) } ACTION_GCM_REGISTER_ALL_ACCOUNTS, ACTION_GCM_CONNECTED -> { @@ -165,134 +115,9 @@ class GcmInGmsService : LifecycleService() { val account = accountManager?.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)?.find { it.name == accountName } ?: return updateLocalAccountGroups(account) } - ACTION_GCM_NOTIFY_COMPLETE -> { - val accountName = intent.getStringExtra(EXTRA_NOTIFICATION_ACCOUNT) ?: return - val notificationList = accountNotificationMap[accountName] ?: return - notificationList.forEach { - val notificationId = it.first - val notificationData = it.second - updateNotificationReadState(accountName, notificationData, NOTIFICATION_STATUS_COMPLETE) - NotificationManagerCompat.from(this).cancel(notificationId) - Log.d(TAG, "Notification with $accountName updateNotificationReadState <$notificationId> to Completed.") - } - accountNotificationMap.remove(accountName) - } - } - } - - private fun getCurrentLanguageTag(): String { - return runCatching { - if (SDK_INT >= 24) { - resources.configuration.locales[0].toLanguageTag() - } else { - val locale = resources.configuration.locale - locale.language + (if (locale.country.isEmpty()) "" else "-" + locale.country) - } - }.getOrDefault(Locale.getDefault().language) - } - - private fun getDensityQualifier(): DeviceInfo.DensityQualifier { - val dpi = resources.displayMetrics.densityDpi - return when { - dpi >= DisplayMetrics.DENSITY_XXHIGH -> DeviceInfo.DensityQualifier.XXHDPI - dpi >= DisplayMetrics.DENSITY_XHIGH -> DeviceInfo.DensityQualifier.XHDPI - dpi >= DisplayMetrics.DENSITY_HIGH -> DeviceInfo.DensityQualifier.HDPI - dpi >= DisplayMetrics.DENSITY_TV -> DeviceInfo.DensityQualifier.TVDPI - dpi >= DisplayMetrics.DENSITY_MEDIUM -> DeviceInfo.DensityQualifier.MDPI - else -> DeviceInfo.DensityQualifier.LDPI - } - } - - private suspend fun requestNotificationInfo(account: Account, notificationData: NotificationData) = suspendCoroutine { sup -> - try { - val response = getGunsApiServiceClient(account, accountManager!!).GmsGnotsFetchByIdentifier().executeBlocking(FetchByIdentifierRequest.Builder().apply { - config(GmsConfig.Builder().apply { - versionInfo(GmsConfig.GmsVersionInfo(Constants.GMS_VERSION_CODE)) - }.build()) - identifiers(NotificationIdentifierList.Builder().apply { - deviceInfo(DeviceInfo.Builder().apply { - densityQualifier(getDensityQualifier()) - localeTag(getCurrentLanguageTag()) - sdkVersion(SDK_INT) - density(resources.displayMetrics.density) - timeZoneId(TimeZone.getDefault().id) - }.build()) - notifications(notificationData.identifier?.let { listOf(it) } ?: emptyList()) - }.build()) - }.build()) - sup.resume(response) - } catch (e: Exception) { - sup.resumeWithException(e) - } - } - - private suspend fun notifyVerificationInfo(data: Bundle) { - Log.d(TAG, "notifyVerificationInfo: from: ${data.getString(GcmConstants.EXTRA_FROM)} data: $data") - val gcmBodyType = data.getString(GcmConstants.EXTRA_GCM_BODY) ?: return - if (GMS_GCM_NOTIFICATIONS != gcmBodyType) return - val payloadData = data.getString(GcmConstants.EXTRA_GMS_GNOTS_PAYLOAD) ?: return - val notificationData = NotificationData.ADAPTER.decode(Base64.decode(payloadData, DEFAULT_FLAGS)) - Log.w(TAG, "notifyVerificationInfo: $notificationData") - if (notificationData.isActive == true) return - val account = notificationData.userInfo?.userId?.let { id -> - accountManager?.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)?.find { - accountManager?.getUserData(it, AuthConstants.GOOGLE_USER_ID) == id - } - } ?: return - Log.d(TAG, "notifyVerificationInfo: account: ${account.name}") - val identifierResponse = withContext(Dispatchers.IO) { - repeat(NOTIFICATION_REPEAT_NUM) { attempt -> - try { - val notificationInfo = requestNotificationInfo(account, notificationData) - if (notificationInfo.notifications?.notificationDataList.isNullOrEmpty()) { - throw RuntimeException("Notification not found") - } - return@withContext notificationInfo - } catch (e: Exception) { - Log.w(TAG, "Attempt ${attempt + 1} failed: ${e.message}") - } - delay(NOTIFICATION_DELAY_TIME) - } - return@withContext null - } - Log.d(TAG, "notifyVerificationInfo: identifierResponse: $identifierResponse") - val notifications = identifierResponse?.notifications?.notificationDataList ?: return - notifications.forEachIndexed { index, it -> - Log.d(TAG, "notifyVerificationInfo: notifications: index:$index it: $it") - updateNotificationReadState(account.name, it, NOTIFICATION_STATUS_READY) - sendNotification(account, notificationIdGenerator.incrementAndGet(), it) - updateNotificationReadState(account.name, it, NOTIFICATION_STATUS_COMPLETE) } } - private fun sendNotification(account: Account, notificationId: Int, notificationData: NotificationData) { - if (notificationData.isActive == true) return - val content = notificationData.content ?: return - val intentExtras = notificationData.intentActions?.primaryPayload?.extras ?: return - val intent = Intent(this, MainActivity::class.java).apply { - `package` = Constants.GMS_PACKAGE_NAME - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK - intentExtras.forEach { putExtra(it.key, it.value_) } - putExtra(KEY_NOTIFICATION_ID, notificationId) - } - val pendingIntent = PendingIntentCompat.getActivity(this, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT, false) - val builder = NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle(content.accountName) - .setContentText(content.description) - .setStyle(NotificationCompat.BigTextStyle().bigText(content.description)) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setContentIntent(pendingIntent) - .setAutoCancel(true) - .setSmallIcon(R.drawable.ic_google_logo) - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == - PackageManager.PERMISSION_GRANTED - ) { - NotificationManagerCompat.from(this).notify(notificationId, builder.build()) - } - runCatching { startActivity(intent) } - accountNotificationMap.getOrPut(account.name) { mutableListOf() }.add(Pair(notificationId, notificationData)) - } - private suspend fun updateGroupsWithAccount(account: Account, regId: String) { Log.d(TAG, "updateGroupsWithAccount: account: ${account.name}") val authManager = AuthManager(this, account.name, Constants.GMS_PACKAGE_NAME, GMS_GCM_OAUTH_SERVICE).apply { @@ -393,57 +218,6 @@ class GcmInGmsService : LifecycleService() { } } - private suspend fun updateNotificationReadState(accountName: String, notificationData: NotificationData, readState: Int) { - if (accountName.isEmpty() || notificationData.identifier?.uniqueId?.isEmpty() == true) { - return - } - try { - val identifier = notificationData.identifier - val readStateList = when { - readState == NOTIFICATION_STATUS_COMPLETE -> { - listOf( - ReadStateItem.Builder().apply { - this.notification = identifier - this.state = null - this.status = readState - }.build() - ) - } - notificationData.content?.actionButtons.isNullOrEmpty() -> { - Log.w(TAG, "No action buttons found, skipping read state update.") - return - } - else -> { - notificationData.content!!.actionButtons.map { - ReadStateItem.Builder().apply { - this.notification = identifier - this.state = it.icon - this.status = readState - }.build() - } - } - } - withContext(Dispatchers.IO) { - sendNotificationReadState(accountName, ReadStateList.Builder().apply { items = readStateList }.build()) - } - Log.i(TAG, "Notification read state updated successfully for account: $accountName") - } catch (e: Exception) { - Log.w(TAG, "Failed to update the notification(s) read state.", e) - } - } - - private fun sendNotificationReadState(accountName: String, readStateList: ReadStateList) { - val account = accountManager?.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)?.find { it.name == accountName } ?: return - getGunsApiServiceClient(account, accountManager!!).GmsGnotsSetReadStates().executeBlocking( - GmsGnotsSetReadStatesRequest.Builder().apply { - config = GmsConfig.Builder().apply { - versionInfo(GmsConfig.GmsVersionInfo(Constants.GMS_VERSION_CODE)) - }.build() - readStates = readStateList - }.build() - ) - } - private fun checkGmsGcmStatus(): Boolean { val targetId = LastCheckinInfo.read(this).androidId val regSender = sp?.getString(KEY_GCM_REG_SENDER, null) @@ -452,65 +226,20 @@ class GcmInGmsService : LifecycleService() { val regTime = sp?.getLong(KEY_GCM_REG_TIME, 0) ?: 0L return targetId != androidId || regSender == null || regId == null || regTime + GCM_GMS_REG_REFRESH_S * 1000 < System.currentTimeMillis() } - - private fun AuthResponse.parseAuthsToken(): String? { - Log.d(TAG, "parseAuthsToken start: auths: $auths itMetadata: $itMetadata") - if (auths.isNullOrEmpty() || itMetadata.isNullOrEmpty()) return null - if (!auths.startsWith(AUTHS_TOKEN_PREFIX)) return null - try { - val tokenBase64 = auths.substring(AUTHS_TOKEN_PREFIX.length) - val authData = ItAuthData.ADAPTER.decode(Base64.decode(tokenBase64, DEFAULT_FLAGS)) - val metadata = ItMetadataData.ADAPTER.decode(Base64.decode(itMetadata, DEFAULT_FLAGS)) - val authorization = OAuthAuthorization.Builder().apply { - effectiveDurationSeconds(min(metadata.liveTime ?: Int.MAX_VALUE, expiresInDurationSec)) - if (metadata.field_?.types?.contains(TokenField.FieldType.SCOPE) == true) { - val scopeIds = metadata.entries.flatMap { entry -> - entry.name.map { scope -> entry to scope } - }.filter { (_, scope) -> - scope in grantedScopes - }.mapNotNull { (entry, _) -> - entry.id - }.toSet() - scopeIds(scopeIds.toList()) - } - }.build() - val oAuthTokenData = OAuthTokenData.Builder().apply { - fieldType(TokenField.FieldType.SCOPE.value) - authorization(authorization.encodeByteString()) - durationMillis(0) - }.build() - val tokenDataBytes = oAuthTokenData.encode() - val secretKey: ByteArray? = authData.signature?.toByteArray() - val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(secretKey, "HmacSHA256")) } - val bytes: ByteArray = mac.doFinal(tokenDataBytes) - val newAuthData = authData.newBuilder().apply { - tokens(arrayListOf(oAuthTokenData.encodeByteString())) - signature(ByteString.of(*bytes)) - }.build() - return AUTHS_TOKEN_PREFIX + Base64.encodeToString(newAuthData.encode(), DEFAULT_FLAGS) - } catch (e: Exception) { - Log.w(TAG, "parseAuthsToken: ", e); - return null; - } - } - - private fun getGunsApiServiceClient(account: Account, accountManager: AccountManager): GunsGmscoreApiServiceClient { - val oauthToken = accountManager.blockingGetAuthToken(account, GMS_NOTS_OAUTH_SERVICE, true) - return createGrpcClient(baseUrl = GMS_NOTS_BASE_URL, oauthToken = oauthToken) - } } class GcmRegistrationReceiver : WakefulBroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val shouldReceiveTwoStepVerification = AuthPrefs.shouldReceiveTwoStepVerification(context) - if (!shouldReceiveTwoStepVerification) { - Log.d(TAG, "GcmRegistrationReceiver onReceive: Switch not allowed ") + val allowedFindDevicesRequest = AuthPrefs.allowedFindDevices(context) + if (!shouldReceiveTwoStepVerification && !allowedFindDevicesRequest) { + Log.d(TAG, "GcmRegistrationReceiver onReceive: Switch not allowed & Switch not allowed") return } Log.d(TAG, "GcmRegistrationReceiver onReceive: action: ${intent.action}") val callIntent = Intent(context, GcmInGmsService::class.java) callIntent.action = intent.action - if (ACTION_GCM_REGISTER_ACCOUNT == intent.action || ACTION_GCM_NOTIFY_COMPLETE == intent.action || GcmConstants.ACTION_C2DM_RECEIVE == intent.action) { + if (ACTION_GCM_REGISTER_ACCOUNT == intent.action || GcmConstants.ACTION_C2DM_RECEIVE == intent.action) { callIntent.putExtras(intent.extras!!) } ForegroundServiceContext(context).startService(callIntent) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/ReceiverService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ReceiverService.kt new file mode 100644 index 0000000000..04cacc6df7 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/ReceiverService.kt @@ -0,0 +1,81 @@ +/** + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.gcm + +import android.app.Service +import android.content.Intent +import android.os.Handler +import android.os.HandlerThread +import android.os.IBinder +import android.os.Message +import android.util.Log +import org.microg.gms.common.ForegroundServiceContext +import java.util.concurrent.atomic.AtomicInteger + +abstract class ReceiverService(private val tag: String): Service() { + @Volatile + private var handlerThread: HandlerThread? = null + + @Volatile + private var handler: Handler? = null + private val atomicFlag = AtomicInteger(0) + + private fun release() { + if (atomicFlag.decrementAndGet() == 0) { + Log.d(tag, "release: ") + stopSelf() + } + } + + override fun onCreate() { + if (!allowed()) { + Log.d(tag, "onCreate: not allowed") + return + } + handlerThread = HandlerThread(tag) + handlerThread?.start() + handler = object : Handler(handlerThread?.looper!!) { + override fun handleMessage(msg: Message) { + val intent = msg.obj as Intent + try { + receiver(intent) + } catch (e: Exception) { + Log.w(tag, "handleMessage: ", e) + } finally { + release() + } + } + } + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + override fun onDestroy() { + Log.d(tag, "onDestroy: ") + handler?.removeCallbacksAndMessages(null) + handlerThread?.quit() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + ForegroundServiceContext.completeForegroundService(this, intent, tag) + if (!allowed()) { + Log.d(tag, "onStartCommand: not allowed") + stopSelf() + return START_NOT_STICKY + } + Log.d(tag, "onStartCommand: start ") + atomicFlag.incrementAndGet() + val messageObtain = Message.obtain() + messageObtain.obj = intent + handler?.sendMessage(messageObtain) + return START_NOT_STICKY + } + + abstract fun allowed(): Boolean + abstract fun receiver(intent: Intent) +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt index 8491604554..f94a5fd50a 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt @@ -5,23 +5,80 @@ package org.microg.gms.gcm +import android.util.Base64 import com.squareup.wire.GrpcClient import com.squareup.wire.Service import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response +import okio.ByteString +import org.microg.gms.auth.AuthResponse +import org.microg.gms.auth.ItAuthData +import org.microg.gms.auth.ItMetadataData +import org.microg.gms.auth.OAuthAuthorization +import org.microg.gms.auth.OAuthTokenData +import org.microg.gms.auth.TokenField +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import kotlin.math.min + +const val ACTION_GCM_MESSAGE_RECEIVE = "org.microg.gms.gcm.MESSAGE_RECEIVE" const val ACTION_GCM_RECONNECT = "org.microg.gms.gcm.RECONNECT" const val ACTION_GCM_CONNECTED = "org.microg.gms.gcm.CONNECTED" const val ACTION_GCM_REGISTER_ACCOUNT = "org.microg.gms.gcm.REGISTER_ACCOUNT" const val ACTION_GCM_REGISTER_ALL_ACCOUNTS = "org.microg.gms.gcm.REGISTER_ALL_ACCOUNTS" -const val ACTION_GCM_NOTIFY_COMPLETE = "org.microg.gms.gcm.NOTIFY_COMPLETE" const val KEY_GCM_REGISTER_ACCOUNT_NAME = "register_account_name" const val EXTRA_NOTIFICATION_ACCOUNT = "notification_account" +const val DEFAULT_FLAGS = Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING const val GMS_NOTS_OAUTH_SERVICE = "oauth2:https://www.googleapis.com/auth/notifications" const val GMS_NOTS_BASE_URL = "https://notifications-pa.googleapis.com" +const val KEY_GCM_ANDROID_ID = "androidId" +const val KEY_GCM_REG_ID = "regId" + +private const val AUTHS_TOKEN_PREFIX = "ya29.m." + +fun AuthResponse.parseAuthsToken(): String? { + if (auths.isNullOrEmpty() || itMetadata.isNullOrEmpty()) return null + if (!auths.startsWith(AUTHS_TOKEN_PREFIX)) return null + try { + val tokenBase64 = auths.substring(AUTHS_TOKEN_PREFIX.length) + val authData = ItAuthData.ADAPTER.decode(Base64.decode(tokenBase64, DEFAULT_FLAGS)) + val metadata = ItMetadataData.ADAPTER.decode(Base64.decode(itMetadata, DEFAULT_FLAGS)) + val authorization = OAuthAuthorization.Builder().apply { + effectiveDurationSeconds(min(metadata.liveTime ?: Int.MAX_VALUE, expiresInDurationSec)) + if (metadata.field_?.types?.contains(TokenField.FieldType.SCOPE) == true) { + val scopeIds = metadata.entries.flatMap { entry -> + entry.name.map { scope -> entry to scope } + }.filter { (_, scope) -> + scope in grantedScopes + }.mapNotNull { (entry, _) -> + entry.id + }.toSet() + scopeIds(scopeIds.toList()) + } + }.build() + val oAuthTokenData = OAuthTokenData.Builder().apply { + fieldType(TokenField.FieldType.SCOPE.value) + authorization(authorization.encodeByteString()) + durationMillis(0) + }.build() + val tokenDataBytes = oAuthTokenData.encode() + val secretKey: ByteArray? = authData.signature?.toByteArray() + val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(secretKey, "HmacSHA256")) } + val bytes: ByteArray = mac.doFinal(tokenDataBytes) + val newAuthData = authData.newBuilder().apply { + tokens(arrayListOf(oAuthTokenData.encodeByteString())) + signature(ByteString.of(*bytes)) + }.build() + return AUTHS_TOKEN_PREFIX + Base64.encodeToString(newAuthData.encode(), DEFAULT_FLAGS) + } catch (e: Exception) { + return null; + } +} + class AuthHeaderInterceptor( private val oauthToken: String, ) : Interceptor { @@ -33,11 +90,11 @@ class AuthHeaderInterceptor( inline fun createGrpcClient( baseUrl: String, - oauthToken: String, - minMessageToCompress: Long = Long.MAX_VALUE + interceptor: Interceptor, + minMessageToCompress: Long = Long.MAX_VALUE, ): S { val client = OkHttpClient.Builder().apply { - addInterceptor(AuthHeaderInterceptor(oauthToken)) + addInterceptor(interceptor) }.build() val grpcClient = GrpcClient.Builder() .client(client) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/registeration/ChimeGmsRegistrationHelper.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/registeration/ChimeGmsRegistrationHelper.kt index 6131b492d2..d82618bee8 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/registeration/ChimeGmsRegistrationHelper.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/registeration/ChimeGmsRegistrationHelper.kt @@ -34,6 +34,7 @@ import org.microg.gms.auth.AuthConstants import org.microg.gms.checkin.LastCheckinInfo import org.microg.gms.common.Constants import org.microg.gms.common.Utils +import org.microg.gms.gcm.AuthHeaderInterceptor import org.microg.gms.gcm.GMS_NOTS_BASE_URL import org.microg.gms.gcm.GMS_NOTS_OAUTH_SERVICE import org.microg.gms.gcm.createGrpcClient @@ -71,7 +72,7 @@ class ChimeGmsRegistrationHelper(val context: Context) { Log.d(TAG, "Registration request: ${request.encode().toBase64()}") val api = createGrpcClient( baseUrl = GMS_NOTS_BASE_URL, - oauthToken = authTokens.first().second + interceptor = AuthHeaderInterceptor(authTokens.first().second) ) runCatching { api.MultiLoginUpdate().executeBlocking(request) }.onFailure { Log.d(TAG, "handleRegistration: failed!", it) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt index 2be044bd99..8acb796e44 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt @@ -42,6 +42,7 @@ val TWO_STATE_SETTINGS = listOf( Auth.INCLUDE_ANDROID_ID, Auth.STRIP_DEVICE_NAME, Auth.TWO_STEP_VERIFICATION, + Auth.FIND_DEVICES, ) class AccountsFragment : PreferenceFragmentCompat() { @@ -80,6 +81,7 @@ class AccountsFragment : PreferenceFragmentCompat() { SettingsContract.setSettings(requireContext(), Auth.getContentUri(requireContext())) { put(preference.key, newValue) } updateSettings() if (preference.key == Auth.TWO_STEP_VERIFICATION && newValue) registerGcmInGms() + if (preference.key == Auth.FIND_DEVICES && newValue) registerGcmInGms() true } else false } diff --git a/play-services-core/src/main/res/values-zh-rCN/strings.xml b/play-services-core/src/main/res/values-zh-rCN/strings.xml index 13f57aeda1..5be76c2dd3 100644 --- a/play-services-core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -84,6 +84,8 @@ 启用后,您设备上的所有应用将可以看到与您 Google 账号关联的电子邮件地址,而无需您的许可。 允许接收 Google 二次验证信息 启用后,设备可以接收来自 Google 的两步验证通知(需启用云端消息推送并保持连接)。 + 允许响应 Google 设备查找 + 启用后,设备可以响应来自 Google 的查找设备通知(需启用云端消息推送并保持连接)。 将您的设备注册到 Google 服务,并创建唯一的设备识别码。microG 将去除注册信息中您 Google 账户名以外的用于识别的信息。 注册设备 状态 diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index c71f247fd3..d2b9119405 100644 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -170,6 +170,8 @@ Please set up a password, PIN, or pattern lock screen." When enabled, authentication requests won\'t include the device\'s name, which may allow unauthorized devices to sign in, but may have unforeseen consequences. Receive two-step verification prompts When enabled, the device can receive two-step verification prompts from Google (Cloud Messaging is required). + Allow responding to Google Device Finder + When enabled, the device can respond to Find My Device notifications from Google (Cloud Messaging is required). Registers your device to Google services and creates a unique device identifier. microG strips identifying bits other than your Google account name from registration data. Android ID diff --git a/play-services-core/src/main/res/xml/preferences_accounts.xml b/play-services-core/src/main/res/xml/preferences_accounts.xml index 9a89fe2157..d20d886cd6 100644 --- a/play-services-core/src/main/res/xml/preferences_accounts.xml +++ b/play-services-core/src/main/res/xml/preferences_accounts.xml @@ -54,5 +54,11 @@ android:summary="@string/pref_auth_two_step_verification_summary" android:title="@string/pref_auth_two_step_verification_title" app:iconSpaceReserved="false" /> +