diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index b776816c2b..793795e4ac 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation project(':play-services-cronet-core') implementation project(':play-services-droidguard-core') implementation project(':play-services-fido-core') + implementation project(':play-services-fitness-core') implementation project(':play-services-gmscompliance-core') implementation project(':play-services-location-core') implementation project(':play-services-location-core-base') diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 0dbd7c07e2..b9d69cb41a 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -831,17 +831,6 @@ - - - - - - - - - - diff --git a/play-services-core/src/main/kotlin/org/microg/gms/signin/SignInService.kt b/play-services-core/src/main/kotlin/org/microg/gms/signin/SignInService.kt index 14bad51a23..b6368ab7b7 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/signin/SignInService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/signin/SignInService.kt @@ -11,7 +11,6 @@ import android.content.Context import android.os.Bundle import android.os.Parcel import android.util.Log -import androidx.core.content.getSystemService import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import com.google.android.gms.common.ConnectionResult @@ -20,6 +19,7 @@ import com.google.android.gms.common.api.Scope import com.google.android.gms.common.internal.* import com.google.android.gms.signin.internal.* import org.microg.gms.BaseService +import org.microg.gms.auth.AuthConstants import org.microg.gms.common.GmsService import org.microg.gms.common.PackageUtils import org.microg.gms.utils.warnOnTransactionIssues @@ -60,10 +60,12 @@ class SignInServiceImpl(val context: Context, override val lifecycle: Lifecycle, override fun signIn(request: SignInRequest?, callbacks: ISignInCallbacks?) { Log.d(TAG, "signIn($request)") - val account = request?.request?.account - val result = if (account == null || context.getSystemService()?.getAccountsByType(account.type)?.contains(account) != true) - ConnectionResult(ConnectionResult.SIGN_IN_REQUIRED) else ConnectionResult(ConnectionResult.SUCCESS) runCatching { + val accountManager = AccountManager.get(context) + val account = request?.request?.account?.let { if (it.name == AuthConstants.DEFAULT_ACCOUNT) accountManager.getAccountsByType(it.type).firstOrNull() else it } + val result = if (account == null || !accountManager.getAccountsByType(account.type).contains(account)) + ConnectionResult(ConnectionResult.SIGN_IN_REQUIRED) else ConnectionResult(ConnectionResult.SUCCESS) + Log.d(TAG, "signIn: account -> ${account?.name}") callbacks?.onSignIn(SignInResponse().apply { connectionResult = result response = ResolveAccountResponse().apply { diff --git a/play-services-fitness/build.gradle b/play-services-fitness/build.gradle index 600a0d69f2..54ad5c74f5 100644 --- a/play-services-fitness/build.gradle +++ b/play-services-fitness/build.gradle @@ -26,9 +26,11 @@ android { } dependencies { + // Dependencies from play-services-fitness:21.2.0 + api 'androidx.collection:collection:1.0.0' api project(':play-services-base') - api project(':play-services-base-core') api project(':play-services-basement') + api project(':play-services-tasks') annotationProcessor project(':safe-parcel-processor') } \ No newline at end of file diff --git a/play-services-fitness/core/build.gradle b/play-services-fitness/core/build.gradle new file mode 100644 index 0000000000..c58d517f73 --- /dev/null +++ b/play-services-fitness/core/build.gradle @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-fitness') + + implementation project(':play-services-base-core') +} + +android { + namespace "org.microg.gms.fitness.core" + + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'MissingTranslation', 'GetLocales' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +// Not publishable yet +// apply from: '../../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-fitness' \ No newline at end of file diff --git a/play-services-fitness/core/src/main/AndroidManifest.xml b/play-services-fitness/core/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..161741daa0 --- /dev/null +++ b/play-services-fitness/core/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/config/FitConfigBroker.kt b/play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/config/FitConfigBroker.kt new file mode 100644 index 0000000000..99cb3819c2 --- /dev/null +++ b/play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/config/FitConfigBroker.kt @@ -0,0 +1,51 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.service.config + +import android.os.Parcel +import android.util.Log +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.fitness.internal.IGoogleFitConfigApi +import com.google.android.gms.fitness.request.DataTypeCreateRequest +import com.google.android.gms.fitness.request.DisableFitRequest +import com.google.android.gms.fitness.request.ReadDataTypeRequest +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "FitConfigBroker" + +class FitConfigBroker : BaseService(TAG, GmsService.FITNESS_CONFIG) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + callback.onPostInitComplete(CommonStatusCodes.SUCCESS, FitConfigBrokerImpl(), null) + } +} + +class FitConfigBrokerImpl : IGoogleFitConfigApi.Stub() { + + override fun createCustomDataType(request: DataTypeCreateRequest?) { + Log.d(TAG, "Not implemented createCustomDataType: $request") + } + + override fun readDataType(request: ReadDataTypeRequest?) { + Log.d(TAG, "Not implemented readDataType: $request") + } + + override fun disableFit(request: DisableFitRequest?) { + Log.d(TAG, "Method Called: $request") + try { + request?.callback?.onResult(Status.SUCCESS) + } catch (e: Exception) { + Log.w(TAG, "disableFit Error $e") + } + } + + 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/com/google/android/gms/fitness/service/history/FitHistoryBroker.kt b/play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/history/FitHistoryBroker.kt similarity index 93% rename from play-services-core/src/main/kotlin/com/google/android/gms/fitness/service/history/FitHistoryBroker.kt rename to play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/history/FitHistoryBroker.kt index 1fefa2c219..349f48b395 100644 --- a/play-services-core/src/main/kotlin/com/google/android/gms/fitness/service/history/FitHistoryBroker.kt +++ b/play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/history/FitHistoryBroker.kt @@ -6,6 +6,7 @@ package com.google.android.gms.fitness.service.history import android.os.Bundle +import android.os.Parcel import android.util.Log import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.internal.GetServiceRequest @@ -27,6 +28,7 @@ import com.google.android.gms.fitness.request.ReadStatsRequest import com.google.android.gms.fitness.request.SessionChangesRequest import org.microg.gms.BaseService import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues private const val TAG = "FitHistoryBroker" @@ -98,4 +100,6 @@ class FitHistoryBrokerImpl : IGoogleFitHistoryApi.Stub() { Log.d(TAG, "Not implemented getSessionChanges: $request") } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/sessions/FitSessionsBroker.kt b/play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/sessions/FitSessionsBroker.kt new file mode 100644 index 0000000000..987801c12d --- /dev/null +++ b/play-services-fitness/core/src/main/kotlin/com/google/android/gms/fitness/service/sessions/FitSessionsBroker.kt @@ -0,0 +1,59 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.service.sessions; + +import android.os.Parcel +import android.util.Log +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.fitness.internal.IGoogleFitSessionsApi +import com.google.android.gms.fitness.request.SessionInsertRequest +import com.google.android.gms.fitness.request.SessionReadRequest +import com.google.android.gms.fitness.request.SessionRegistrationRequest +import com.google.android.gms.fitness.request.SessionStartRequest +import com.google.android.gms.fitness.request.SessionStopRequest +import com.google.android.gms.fitness.request.SessionUnregistrationRequest +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "FitSessionsBroker" + +class FitSessionsBroker : BaseService(TAG, GmsService.FITNESS_SESSIONS) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + callback.onPostInitComplete(CommonStatusCodes.SUCCESS, FitSessionsBrokerImpl(), null) + } +} + +class FitSessionsBrokerImpl : IGoogleFitSessionsApi.Stub() { + override fun startRequest(startRequest: SessionStartRequest?) { + Log.d(TAG, "Not implemented startRequest: $startRequest") + } + + override fun stopRequest(stopRequest: SessionStopRequest?) { + Log.d(TAG, "Not implemented stopRequest: $stopRequest") + } + + override fun insertRequest(insetRequest: SessionInsertRequest?) { + Log.d(TAG, "Not implemented insertRequest: $insetRequest") + } + + override fun readRequest(readRequest: SessionReadRequest?) { + Log.d(TAG, "Not implemented readRequest: $readRequest") + } + + override fun registrationRequest(registrationRequest: SessionRegistrationRequest?) { + Log.d(TAG, "Not implemented registrationRequest: $registrationRequest") + } + + override fun unRegistrationRequest(unRegistrationRequest: SessionUnregistrationRequest?) { + Log.d(TAG, "Not implemented unRegistrationRequest: $unRegistrationRequest") + } + + 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-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IDataTypeCallback.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IDataTypeCallback.aidl new file mode 100644 index 0000000000..f1b9995c6e --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IDataTypeCallback.aidl @@ -0,0 +1,12 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.internal; + +import com.google.android.gms.fitness.result.DataTypeResult; + +interface IDataTypeCallback { + void onDataType(in DataTypeResult dataTypeResult) = 0; +} \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IGoogleFitConfigApi.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IGoogleFitConfigApi.aidl new file mode 100644 index 0000000000..2ab86aa9e9 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IGoogleFitConfigApi.aidl @@ -0,0 +1,16 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.internal; + +import com.google.android.gms.fitness.request.DataTypeCreateRequest; +import com.google.android.gms.fitness.request.DisableFitRequest; +import com.google.android.gms.fitness.request.ReadDataTypeRequest; + +interface IGoogleFitConfigApi { + void createCustomDataType(in DataTypeCreateRequest request) = 0; + void readDataType(in ReadDataTypeRequest request) = 1; + void disableFit(in DisableFitRequest request) = 21; +} diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IGoogleFitSessionsApi.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IGoogleFitSessionsApi.aidl new file mode 100644 index 0000000000..45433abc56 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IGoogleFitSessionsApi.aidl @@ -0,0 +1,21 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package com.google.android.gms.fitness.internal; + +import com.google.android.gms.fitness.request.SessionStartRequest; +import com.google.android.gms.fitness.request.SessionStopRequest; +import com.google.android.gms.fitness.request.SessionInsertRequest; +import com.google.android.gms.fitness.request.SessionReadRequest; +import com.google.android.gms.fitness.request.SessionRegistrationRequest; +import com.google.android.gms.fitness.request.SessionUnregistrationRequest; + +interface IGoogleFitSessionsApi { + void startRequest(in SessionStartRequest startRequest) = 0; + void stopRequest(in SessionStopRequest stopRequest) = 1; + void insertRequest(in SessionInsertRequest insetRequest) = 2; + void readRequest(in SessionReadRequest readRequest) = 3; + void registrationRequest(in SessionRegistrationRequest registrationRequest) = 4; + void unRegistrationRequest(in SessionUnregistrationRequest unRegistrationRequest) = 5; +} \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/ISessionReadCallback.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/ISessionReadCallback.aidl new file mode 100644 index 0000000000..eb85d8b370 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/ISessionReadCallback.aidl @@ -0,0 +1,11 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package com.google.android.gms.fitness.internal; + +import com.google.android.gms.fitness.result.SessionReadResult; + +interface ISessionReadCallback { + void onResult(in SessionReadResult sessionReadResult) = 0; +} \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/ISessionStopCallback.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/ISessionStopCallback.aidl new file mode 100644 index 0000000000..23a925432b --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/ISessionStopCallback.aidl @@ -0,0 +1,11 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package com.google.android.gms.fitness.internal; + +import com.google.android.gms.fitness.result.SessionStopResult; + +interface ISessionStopCallback { + void onResult(in SessionStopResult sessionStopReult) = 0; +} \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IStatusCallback.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IStatusCallback.aidl index 99544d738d..63e0483bd4 100644 --- a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IStatusCallback.aidl +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/internal/IStatusCallback.aidl @@ -8,5 +8,5 @@ package com.google.android.gms.fitness.internal; import com.google.android.gms.common.api.Status; interface IStatusCallback { - void onResult(in Status status) = 1; + void onResult(in Status status) = 0; } \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/DataTypeCreateRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/DataTypeCreateRequest.aidl new file mode 100644 index 0000000000..c786ea07f7 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/DataTypeCreateRequest.aidl @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable DataTypeCreateRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/DisableFitRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/DisableFitRequest.aidl new file mode 100644 index 0000000000..5011dad1bf --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/DisableFitRequest.aidl @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable DisableFitRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/ReadDataTypeRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/ReadDataTypeRequest.aidl new file mode 100644 index 0000000000..774883a504 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/ReadDataTypeRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable ReadDataTypeRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionInsertRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionInsertRequest.aidl new file mode 100644 index 0000000000..62c1211ef2 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionInsertRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable SessionInsertRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionReadRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionReadRequest.aidl new file mode 100644 index 0000000000..e419c939a1 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionReadRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable SessionReadRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionRegistrationRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionRegistrationRequest.aidl new file mode 100644 index 0000000000..4163a42c1c --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionRegistrationRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable SessionRegistrationRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionStartRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionStartRequest.aidl new file mode 100644 index 0000000000..d56ebc2a64 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionStartRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable SessionStartRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionStopRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionStopRequest.aidl new file mode 100644 index 0000000000..bf428185c9 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionStopRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable SessionStopRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionUnregistrationRequest.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionUnregistrationRequest.aidl new file mode 100644 index 0000000000..2b14eb5c90 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/request/SessionUnregistrationRequest.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +parcelable SessionUnregistrationRequest; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/DataTypeResult.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/DataTypeResult.aidl new file mode 100644 index 0000000000..9d5a0b4837 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/DataTypeResult.aidl @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.result; + +parcelable DataTypeResult; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/SessionReadResult.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/SessionReadResult.aidl new file mode 100644 index 0000000000..b0b4c0fd0b --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/SessionReadResult.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.result; + +parcelable SessionReadResult; \ No newline at end of file diff --git a/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/SessionStopResult.aidl b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/SessionStopResult.aidl new file mode 100644 index 0000000000..eabd31d4d9 --- /dev/null +++ b/play-services-fitness/src/main/aidl/com/google/android/gms/fitness/result/SessionStopResult.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.result; + +parcelable SessionStopResult; \ No newline at end of file diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/AppInfo.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Application.java similarity index 53% rename from play-services-fitness/src/main/java/com/google/android/gms/fitness/data/AppInfo.java rename to play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Application.java index b4dea5d67d..cff0c6206f 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/AppInfo.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Application.java @@ -13,29 +13,34 @@ import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import org.microg.gms.common.Constants; +import org.microg.gms.common.Hide; import org.microg.gms.utils.ToStringHelper; @SafeParcelable.Class -public class AppInfo extends AbstractSafeParcelable { +@Hide +public class Application extends AbstractSafeParcelable { - public static final AppInfo DEFAULT = new AppInfo("com.google.android.gms"); + public static final Application GMS_APP = new Application(Constants.GMS_PACKAGE_NAME); - @Field(1) - public String packageName; + @Field(value = 1, getterName = "getPackageName") + @NonNull + private final String packageName; - public AppInfo() { + @Constructor + public Application(@Param(1) @NonNull String packageName) { + this.packageName = packageName; } - public AppInfo(String packageName) { - this.packageName = packageName; + @NonNull + public String getPackageName() { + return packageName; } @NonNull @Override public String toString() { - return ToStringHelper.name("AppInfo") - .field("packageName", packageName) - .end(); + return ToStringHelper.name("Application").value(packageName).end(); } @Override @@ -43,6 +48,6 @@ public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(AppInfo.class); + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(Application.class); } diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Bucket.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Bucket.java index 1eaf0c49e7..a2cc6f33ee 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Bucket.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Bucket.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2023 microG Project Team * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.fitness.data; @@ -9,6 +12,7 @@ import androidx.annotation.NonNull; +import androidx.annotation.Nullable; 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; @@ -16,27 +20,120 @@ import org.microg.gms.utils.ToStringHelper; import java.util.List; +import java.util.concurrent.TimeUnit; @SafeParcelable.Class public class Bucket extends AbstractSafeParcelable { + /** + * Type constant denoting that bucketing by time is requested. + */ public static final int TYPE_TIME = 1; + /** + * Type constant denoting that bucketing by session is requested. + */ public static final int TYPE_SESSION = 2; + /** + * Type constant denoting that bucketing by activity type is requested. + */ public static final int TYPE_ACTIVITY_TYPE = 3; + /** + * Type constant denoting that bucketing by individual activity segment is requested. + */ public static final int TYPE_ACTIVITY_SEGMENT = 4; - @Field(1) - public long startTimeMillis; - @Field(2) - public long endTimeMillis; - @Field(3) - public Session session; - @Field(4) - public int activityType; - @Field(5) - public List dataSets; - @Field(6) - public int bucketType; + @Field(value = 1, getterName = "getStartTimeMillis") + private final long startTimeMillis; + @Field(value = 2, getterName = "getEndTimeMillis") + private final long endTimeMillis; + @Field(value = 3, getterName = "getSession") + @Nullable + private final Session session; + @Field(value = 4, getterName = "getActivityType") + private final int activityType; + @Field(value = 5, getterName = "getDataSets") + private final List dataSets; + @Field(value = 6, getterName = "getBucketType") + private final int bucketType; + + @Constructor + public Bucket(@Param(1) long startTimeMillis, @Param(2) long endTimeMillis, @Nullable @Param(3) Session session, @Param(4) int activityType, @Param(5) List dataSets, @Param(6) int bucketType) { + this.startTimeMillis = startTimeMillis; + this.endTimeMillis = endTimeMillis; + this.session = session; + this.activityType = activityType; + this.dataSets = dataSets; + this.bucketType = bucketType; + } + + /** + * Returns the activity of the bucket if bucketing by activity was requested, or {@link FitnessActivities#UNKNOWN} otherwise. + */ + @NonNull + public String getActivity() { + // TODO + return null; + } + + /** + * Returns the type of the bucket. + */ + public int getBucketType() { + return bucketType; + } + + /** + * Returns the data set of requested data type over the time interval of the bucket. Returns null, if data set for the requested type is not found. + */ + public DataSet getDataSet(@NonNull DataType dataType) { + for (DataSet dataSet : this.dataSets) { + if (dataSet.getDataType().equals(dataType)) { + return dataSet; + } + } + return null; + } + + /** + * Returns the requested data sets over the time interval of the bucket. + */ + public List getDataSets() { + return dataSets; + } + + /** + * Returns the end time of the bucket, in the given time unit since epoch. + */ + public long getEndTime(TimeUnit timeUnit) { + return timeUnit.convert(this.endTimeMillis, TimeUnit.MILLISECONDS); + } + + /** + * Returns the session of the bucket if bucketing by session was requested, {@code null} otherwise. + */ + @Nullable + public Session getSession() { + return session; + } + + /** + * Returns the start time of the bucket, in the given time unit since epoch. + */ + public long getStartTime(@NonNull TimeUnit timeUnit) { + return timeUnit.convert(this.startTimeMillis, TimeUnit.MILLISECONDS); + } + + int getActivityType() { + return activityType; + } + + long getEndTimeMillis() { + return endTimeMillis; + } + + long getStartTimeMillis() { + return startTimeMillis; + } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataPoint.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataPoint.java new file mode 100644 index 0000000000..573815313c --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataPoint.java @@ -0,0 +1,394 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fitness.data; + +import android.content.Intent; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import androidx.annotation.Nullable; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@SafeParcelable.Class +public class DataPoint extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getDataSource") + @NonNull + private final DataSource dataSource; + @Field(value = 3, getterName = "getTimestampNanos") + private long timestampNanos; + @Field(value = 4, getterName = "getStartTimeNanos") + private long startTimeNanos; + @Field(value = 5, getterName = "getValues") + private final Value[] values; + @Field(value = 6, getterName = "getOriginalDataSourceIfSet") + @Nullable + private DataSource originalDataSource; + @Field(value = 7, getterName = "getRawTimestamp") + private final long rawTimestamp; + + DataPoint(DataSource dataSource) { + this.dataSource = dataSource; + List fields = dataSource.getDataType().getFields(); + this.values = new Value[fields.size()]; + for (int i = 0; i < fields.size(); i++) { + values[i] = new Value(fields.get(i).getFormat()); + } + this.rawTimestamp = 0; + } + + @Constructor + DataPoint(@Param(1) @NonNull DataSource dataSource, @Param(3) long timestampNanos, @Param(4) long startTimeNanos, @Param(5) Value[] values, @Param(6) @Nullable DataSource originalDataSource, @Param(7) long rawTimestamp) { + this.dataSource = dataSource; + this.timestampNanos = timestampNanos; + this.startTimeNanos = startTimeNanos; + this.values = values; + this.originalDataSource = originalDataSource; + this.rawTimestamp = rawTimestamp; + } + + /** + * Returns the data source for the data point. If the data point is part of a {@link DataSet}, this will correspond to the data set's data source. + */ + @NonNull + public DataSource getDataSource() { + return dataSource; + } + + /** + * Returns the data type defining the format of the values in this data point. + */ + @NonNull + public DataType getDataType() { + return dataSource.getDataType(); + } + + /** + * Returns the end time of the interval represented by this data point, in the given unit since epoch. This method is equivalent to + * {@link #getTimestamp(TimeUnit)} + */ + public long getEndTime(@NonNull TimeUnit timeUnit) { + return timeUnit.convert(this.timestampNanos, TimeUnit.NANOSECONDS); + } + + /** + * Returns the original data source for this data point. The original data source helps identify the source of the data point as it gets merged and + * transformed into different streams. + *

+ * Note that, if this data point is part of a {@link DataSet}, the data source returned here may be different from the data set's data source. In case of + * transformed or merged data sets, each data point's original data source will retain the original attribution as much as possible, while the + * data set's data source will represent the merged or transformed stream. + *

+ * WARNING: do not rely on this field for anything other than debugging. The value of this field, if it is set at all, is an implementation detail and + * is not guaranteed to remain consistent. + */ + @NonNull + public DataSource getOriginalDataSource() { + if (originalDataSource != null) return originalDataSource; + return dataSource; + } + + /** + * Returns the start time of the interval represented by this data point, in the given unit since epoch. + */ + public long getStartTime(@NonNull TimeUnit timeUnit) { + return timeUnit.convert(this.startTimeNanos, TimeUnit.NANOSECONDS); + } + + /** + * Returns the timestamp of the data point, in the given unit since epoch. For data points that represent intervals, this method will return the + * end time. + */ + public long getTimestamp(@NonNull TimeUnit timeUnit) { + return timeUnit.convert(this.timestampNanos, TimeUnit.NANOSECONDS); + } + + /** + * Returns the value holder for the field with the given name. This method can be used both to query the value and to set it. + * + * @param field One of the fields of this data type. + * @return The Value associated with the given field. + * @throws IllegalArgumentException If the given field doesn't match any of the fields for this DataPoint's data type. + */ + @NonNull + public Value getValue(com.google.android.gms.fitness.data.Field field) { + return this.values[getDataType().indexOf(field)]; + } + + long getTimestampNanos() { + return timestampNanos; + } + + long getStartTimeNanos() { + return startTimeNanos; + } + + Value[] getValues() { + return values; + } + + DataSource getOriginalDataSourceIfSet() { + return originalDataSource; + } + + long getRawTimestamp() { + return rawTimestamp; + } + + /** + * Sets the values of this data point, where the format for all of its values is float. + * + * @param values The value for each field of the data point, in order. + * @deprecated Use {@link DataPoint.Builder} to create {@link DataPoint} instances. + */ + @Deprecated + public DataPoint setFloatValues(float... values) { + if (values.length != this.getDataType().getFields().size()) + throw new IllegalArgumentException("The number of values does not match the number of fields"); + for (int i = 0; i < values.length; i++) { + this.values[i].setFloat(values[i]); + } + return this; + } + + /** + * Sets the values of this data point, where the format for all of its values is int. + * + * @param values The value for each field of the data point, in order. + * @deprecated Use {@link DataPoint.Builder} to create {@link DataPoint} instances. + */ + @Deprecated + public DataPoint setIntValues(int... values) { + if (values.length != this.getDataType().getFields().size()) + throw new IllegalArgumentException("The number of values does not match the number of fields"); + for (int i = 0; i < values.length; i++) { + this.values[i].setInt(values[i]); + } + return this; + } + + /** + * Sets the time interval of a data point that represents an interval of time. For data points that represent instantaneous readings, + * {@link #setTimestamp(long, TimeUnit)} should be used. + * + * @param startTime The start time in the given unit, representing elapsed time since epoch. + * @param endTime The end time in the given unit, representing elapsed time since epoch. + * @param timeUnit The time unit of both start and end timestamps. + * @deprecated Use {@link DataPoint.Builder} to create {@link DataPoint} instances. + */ + @Deprecated + public DataPoint setTimeInterval(long startTime, long endTime, TimeUnit timeUnit) { + this.startTimeNanos = timeUnit.toNanos(startTime); + this.timestampNanos = timeUnit.toNanos(endTime); + return this; + } + + /** + * Sets the timestamp of a data point that represent an instantaneous reading, measurement, or input. For data points that represent intervals, + * {@link #setTimeInterval(long, long, TimeUnit)} should be used. + * + * @param timestamp The timestamp in the given unit, representing elapsed time since epoch. + * @param timeUnit The unit of the given timestamp. + * @deprecated Use {@link DataPoint.Builder} to create {@link DataPoint} instances. + */ + @Deprecated + public DataPoint setTimestamp(long timestamp, TimeUnit timeUnit) { + this.timestampNanos = timeUnit.toNanos(timestamp); + return this; + } + + /** + * Creates a new builder for a {@link DataPoint} with the given {@code dataSource}. + * + * @throws NullPointerException If specified data source is null. + */ + @NonNull + public static Builder builder(@NonNull DataSource dataSource) { + return new Builder(dataSource); + } + + /** + * Creates a new data point for the given dataSource. An unset {@link Value} is created for each field of the data source's data type. + * + * @return An empty data point instance. + * @deprecated Use {@link DataPoint.Builder} to create {@link DataPoint} instances. + */ + @NonNull + @Deprecated + public static DataPoint create(@NonNull DataSource dataSource) { + return new DataPoint(dataSource); + } + + /** + * Extracts a data point from a callback intent received after registering to a data source with a PendingIntent. + * + * @return The extracted DataPoint, or {@code null} if the given intent does not contain a DataPoint + */ + @Nullable + public static DataPoint extract(@NonNull Intent intent) { + return SafeParcelableSerializer.deserializeFromBytes(intent.getByteArrayExtra("com.google.android.gms.fitness.EXTRA_DATA_POINT"), CREATOR); + } + + /** + * Builder for {@link DataPoint} instances. + */ + public static class Builder { + private final DataPoint dataPoint; + private boolean built = false; + + Builder(DataSource dataSource) { + this.dataPoint = DataPoint.create(dataSource); + } + + /** + * Builds and returns the {@link DataPoint}. + */ + @NonNull + public DataPoint build() { + if (built) throw new IllegalStateException("DataPoint already built"); + this.built = true; + return this.dataPoint; + } + + /** + * Sets the value of an activity field to {@code activity}. + * + * @throws IllegalArgumentException If the given index is out of the range for this data type. + * @throws IllegalStateException If the field isn't of format {@link com.google.android.gms.fitness.data.Field#FORMAT_INT32}. + */ + @NonNull + public Builder setActivityField(@NonNull com.google.android.gms.fitness.data.Field field, @NonNull String activity) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.getValue(field).setActivity(activity); + return this; + } + + /** + * Sets the floating point value of the given {@code field} to {@code value}. + * + * @throws IllegalArgumentException If the given index is out of the range for this data type. + * @throws IllegalStateException If the field isn't of format {@link com.google.android.gms.fitness.data.Field#FORMAT_FLOAT}. + */ + @NonNull + public Builder setField(@NonNull com.google.android.gms.fitness.data.Field field, float value) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.getValue(field).setFloat(value); + return this; + } + + + /** + * Sets the map value of the given {@code field} to {@code value}. + * + * @throws IllegalArgumentException If the given index is out of the range for this data type. + * @throws IllegalStateException If the field isn't of format {@link com.google.android.gms.fitness.data.Field#FORMAT_MAP}. + */ + @NonNull + public Builder setField(@NonNull com.google.android.gms.fitness.data.Field field, @NonNull Map map) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.getValue(field).setMap(map); + return this; + } + + + /** + * Sets the integer value of the given {@code field} to {@code value}. + * + * @throws IllegalArgumentException If the given index is out of the range for this data type. + * @throws IllegalStateException If the field isn't of format {@link com.google.android.gms.fitness.data.Field#FORMAT_INT32}. + */ + @NonNull + public Builder setField(@NonNull com.google.android.gms.fitness.data.Field field, int value) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.getValue(field).setInt(value); + return this; + } + + /** + * Sets the string value of the given {@code field} to {@code value}. + * + * @throws IllegalArgumentException If the given index is out of the range for this data type. + * @throws IllegalStateException If the field isn't of format {@link com.google.android.gms.fitness.data.Field#FORMAT_STRING}. + */ + @NonNull + public Builder setField(@NonNull com.google.android.gms.fitness.data.Field field, @NonNull String value) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.getValue(field).setString(value); + return this; + } + + /** + * Sets the values of the data point, where the format for all of its values is float. + * + * @param values The value for each field of the data point, in order. + */ + @NonNull + public Builder setFloatValues(@NonNull float... values) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.setFloatValues(values); + return this; + } + + /** + * Sets the values of the data point, where the format for all of its values is int. + * + * @param values The value for each field of the data point, in order. + */ + @NonNull + public Builder setIntValues(@NonNull int... values) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.setIntValues(values); + return this; + } + + /** + * Sets the time interval of a data point that represents an interval of time. For data points that represent instantaneous readings, + * {@link #setTimestamp(long, TimeUnit)} should be used. + * + * @param startTime The start time in the given unit, representing elapsed time since epoch. + * @param endTime The end time in the given unit, representing elapsed time since epoch. + * @param timeUnit The time unit of both start and end timestamps. + */ + @NonNull + public Builder setTimeInterval(long startTime, long endTime, @NonNull TimeUnit timeUnit) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.setTimeInterval(startTime, endTime, timeUnit); + return this; + } + + /** + * Sets the timestamp of a data point that represent an instantaneous reading, measurement, or input. For data points that represent intervals, + * {@link #setTimeInterval(long, long, TimeUnit)} should be used. + * + * @param timestamp The timestamp in the given unit, representing elapsed time since epoch. + * @param timeUnit The unit of the given timestamp. + */ + @NonNull + public Builder setTimestamp(long timestamp, @NonNull TimeUnit timeUnit) { + if (built) throw new IllegalStateException("DataPoint already built"); + this.dataPoint.setTimestamp(timestamp, timeUnit); + return this; + } + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(DataPoint.class); + +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSet.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSet.java index e96d311ed6..ab0a51cbbc 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSet.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSet.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2023 microG Project Team * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.fitness.data; @@ -13,19 +16,196 @@ import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a fixed set of data points in a data type's stream from a particular data source. A data set usually represents data at + * fixed time boundaries, and can be used both for batch data insertion and as a result of read requests. + */ @SafeParcelable.Class public class DataSet extends AbstractSafeParcelable { @Field(1000) - public int versionCode; - @Field(1) - public DataSource dataSource; + final int versionCode; + @Field(value = 1, getterName = "getDataSource") + @NonNull + private final DataSource dataSource; + @Field(value = 3, getterName = "getRawDataPoints") + @NonNull + private final List rawDataPoints; + @Field(value = 4, getterName = "getUniqueDataSources") + @NonNull + private final List uniqueDataSources; + + @Constructor + DataSet(@Param(1000) int versionCode, @Param(1) @NonNull DataSource dataSource, @Param(3) @NonNull List rawDataPoints, @Param(4) List uniqueDataSources) { + this.versionCode = versionCode; + this.dataSource = dataSource; + this.rawDataPoints = rawDataPoints; + this.uniqueDataSources = versionCode < 2 ? Collections.singletonList(dataSource) : uniqueDataSources; + } + + DataSet(@NonNull DataSource dataSource) { + this.versionCode = 3; + this.dataSource = dataSource; + this.rawDataPoints = new ArrayList<>(); + this.uniqueDataSources = new ArrayList<>(); + uniqueDataSources.add(dataSource); + } + + /** + * Adds a data point to this data set. The data points should be for the correct data type and data source, and should have its timestamp + * already set. + * + * @throws IllegalArgumentException If dataPoint has invalid data. + * @deprecated Build {@link DataSet} instances using the builder. + */ + @Deprecated + public void add(@NonNull DataPoint dataPoint) { + if (!dataPoint.getDataSource().getStreamIdentifier().equals(dataSource.getStreamIdentifier())) + throw new IllegalArgumentException("Conflicting data sources found"); + // TODO + rawDataPoints.add(dataPoint); + } + + /** + * Adds a list of data points to this data set in bulk. All data points should be for the correct data type and data source, and should have their + * timestamp already set. + * + * @deprecated Build {@link DataSet} instances using the builder. + */ + @Deprecated + public void addAll(@NonNull Iterable iterable) { + for (DataPoint dataPoint : iterable) { + add(dataPoint); + } + } + + /** + * Creates an empty data point for this data set's data source. The new data point is not added to the data set by this method. After the data + * point is initialized, {@link #add(DataPoint)} should be called. + */ + @NonNull + public DataPoint createDataPoint() { + return DataPoint.create(this.dataSource); + } + + /** + * Returns the list of data points represented by this data set. The data points will preserve the same order in which they were inserted. + *

+ * Certain APIs that return a DataSet might insert data points in chronological order, but this isn't enforced. + */ + @NonNull + public List getDataPoints() { + return Collections.unmodifiableList(rawDataPoints); + } + + /** + * Returns the data source which this data set represents. All of the data points in the data set are from this data source. + */ + @NonNull + public DataSource getDataSource() { + return dataSource; + } + + /** + * Returns the data type this data set represents. All of the data points in the data set are of this data type. + */ + @NonNull + public DataType getDataType() { + return dataSource.getDataType(); + } + + @NonNull + List getRawDataPoints() { + return rawDataPoints; + } + + @NonNull + List getUniqueDataSources() { + return uniqueDataSources; + } + + /** + * Creates a new builder for a {@link DataSet} with the given {@code dataSource}. + * + * @throws NullPointerException If specified data source is null. + */ + @NonNull + public static Builder builder(@NonNull DataSource dataSource) { + return new Builder(dataSource); + } + + /** + * Creates a new data set to hold data points for the given {@code dataSource}. + *

+ * Data points with the matching data source can be created using {@link #createDataPoint()}, and after having the values set added to the data set + * via {@link #add(DataPoint)}. + * + * @throws NullPointerException If specified data source is null. + */ + @NonNull + public static DataSet create(@NonNull DataSource dataSource) { + return new DataSet(dataSource); + } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } + /** + * Builder used to create new data sets. + */ + public static class Builder { + private final DataSet dataSet; + private boolean built = false; + + Builder(DataSource dataSource) { + this.dataSet = DataSet.create(dataSource); + } + + /** + * Adds a data point to this data set. The data points should be for the correct data type and data source, and should have its timestamp + * already set. + * + * @throws IllegalArgumentException If dataPoint has the wrong {@link DataSource}, or contain invalid data. + */ + @NonNull + public Builder add(@NonNull DataPoint dataPoint) { + if (built) throw new IllegalStateException("DataSet has already been built."); + this.dataSet.add(dataPoint); + return this; + } + + /** + * Adds a list of data points to this data set in bulk. All data points should be for the correct data type and data source, and should have their + * timestamp already set. + * + * @throws IllegalArgumentException If the {@code dataPoints} have the wrong source, or contain invalid data. + */ + @NonNull + public Builder addAll(@NonNull Iterable iterable) { + if (built) throw new IllegalStateException("DataSet has already been built."); + this.dataSet.addAll(iterable); + return this; + } + + /** + * Finishes building and returns the {@link DataSet}. + * + * @throws IllegalStateException If called more than once. + */ + @NonNull + public DataSet build() { + if (built) throw new IllegalStateException("DataSet has already been built."); + this.built = true; + return this.dataSet; + } + } + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(DataSet.class); } diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSource.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSource.java index 93d0aa6f89..8c6e2d7a60 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSource.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataSource.java @@ -1,37 +1,284 @@ /* * SPDX-FileCopyrightText: 2023 microG Project Team * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.fitness.data; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Parcel; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; +import org.microg.gms.common.Constants; +import org.microg.gms.utils.ToStringHelper; +/** + * Definition of a unique source of sensor data. Data sources can expose raw data coming from hardware sensors on local or companion + * devices. They can also expose derived data, created by transforming or merging other data sources. Multiple data sources can exist for the + * same data type. Every data point inserted into or read from Google Fit has an associated data source. + *

+ * The data source contains enough information to uniquely identify its data, including the hardware device and the application that + * collected and/or transformed the data. It also holds useful metadata, such as a stream name and the device type. + *

+ * The data source's data stream can be accessed in a live fashion by registering a data source listener, or via queries over fixed time intervals. + *

+ * An end-user-visible name for the data stream can be set by calling {@link DataSource.Builder.setStreamName(String)} or otherwise computed + * from the device model and application name. + */ @SafeParcelable.Class public class DataSource extends AbstractSafeParcelable { - @Field(1) - public DataType dataType; - @Field(3) - public int type; - @Field(4) - public Device device; - @Field(5) - public AppInfo appInfo; - @Field(6) - public String info; + /** + * Name for the parcelable intent extra containing a data source. It can be extracted using {@link #extract(Intent)}. + */ + public static final String EXTRA_DATA_SOURCE = "vnd.google.fitness.data_source"; + + /** + * Type constant for a data source which exposes original, raw data from an external source such as a hardware sensor, a wearable device, or + * user input. + */ + public static final int TYPE_RAW = 0; + + /** + * Type constant for a data source which exposes data which is derived from one or more existing data sources by performing + * transformations on the original data. + */ + public static final int TYPE_DERIVED = 1; + + @Field(value = 1, getterName = "getDataType") + @NonNull + private final DataType dataType; + @Field(value = 3, getterName = "getType") + private final int type; + @Field(value = 4, getterName = "getDevice") + @Nullable + private final Device device; + @Field(value = 5, getterName = "getApplication") + @Nullable + final Application application; + @Field(value = 6, getterName = "getStreamName") + private final String streamName; + + @Constructor + DataSource(@Param(1) @NonNull DataType dataType, @Param(3) int type, @Param(4) @Nullable Device device, @Param(5) @Nullable Application application, @Param(6) String streamName) { + this.dataType = dataType; + this.type = type; + this.device = device; + this.application = application; + this.streamName = streamName; + } + + @Nullable + public Application getApplication() { + return application; + } + + /** + * Returns the package name for the application responsible for setting the data, or {@code null} if unset/unknown. {@link PackageManager} can be used to + * query relevant information about the application, such as the name, icon, and logo. + *

+ * Data coming from local sensors or BLE devices will not have a corresponding application. + */ + @Nullable + public String getAppPackageName() { + if (application == null) return null; + return application.getPackageName(); + } + + /** + * Returns the data type for data coming from this data source. Knowing the type of a data source can be useful to perform transformations on + * top of raw data without using sources that are themselves computed by transforming raw data. + */ + @NonNull + public DataType getDataType() { + return dataType; + } + + /** + * Returns the device where data is being collected, or {@code null} if unset. + */ + @Nullable + public Device getDevice() { + return device; + } + + /** + * Returns a unique identifier for the data stream produced by this data source. The identifier includes, in order: + *

    + *
  1. the data source's type (raw or derived)
  2. + *
  3. the data source's data type
  4. + *
  5. the application's package name (unique for a given application)
  6. + *
  7. the physical device's manufacturer, model, and serial number (UID)
  8. + *
  9. the data source's stream name.
  10. + *
+ */ + @NonNull + public String getStreamIdentifier() { + StringBuilder sb = new StringBuilder(); + sb.append(type == TYPE_RAW ? "raw" : "derived"); + sb.append(":").append(dataType.getName()); + if (application != null) sb.append(":").append(application.getPackageName()); + if (device != null) sb.append(":").append(device.getDeviceId()); + if (streamName != null) sb.append(":").append(streamName); + return sb.toString(); + } + + /** + * Returns the specific stream name for the stream coming from this data source, or an empty string if unset. + */ + @NonNull + public String getStreamName() { + return streamName; + } + + /** + * Returns the constant describing the type of this data source. + * + * @return One of the constant values ({@link #TYPE_DERIVED} or {@link #TYPE_RAW}), zero if unset. Values outside of this range should be treated as + * unset/unknown. + */ + public int getType() { + return type; + } + + @Override + public int hashCode() { + return getStreamIdentifier().hashCode(); + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("DataSource").value(getStreamIdentifier()).end(); + } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } + /** + * Extracts the data source extra from the given intent, such as an intent to view user's data. + * + * @return The data source, or {@code null} if not found. + */ + @Nullable + public static DataSource extract(@NonNull Intent intent) { + return SafeParcelableSerializer.deserializeFromBytes(intent.getByteArrayExtra(EXTRA_DATA_SOURCE), CREATOR); + } + + /** + * A builder that can be used to construct new data source objects. In general, a built data source should be saved in memory to avoid the cost + * of re-constructing it for every request. + */ + public static class Builder { + private DataType dataType; + private Device device; + private Application application; + private int type = -1; + private String streamName = ""; + + /** + * Finishes building the data source and returns a DataSource object. + * + * @throws IllegalStateException If the builder didn't have enough data to build a valid data source. + */ + @NonNull + public DataSource build() { + if (dataType == null) throw new IllegalStateException("dataType must be set"); + if (type < 0) throw new IllegalStateException("type must be set"); + return new DataSource(dataType, type, device, application, streamName); + } + + /** + * Sets the package name for the application that is recording or computing the data. Used for data sources that aren't built into the platform + * (local sensors and BLE sensors are built-in). It can be used to identify the data source, to disambiguate between data from different + * applications, and also to link back to the original application for a detailed view. + */ + @NonNull + public Builder setAppPackageName(@NonNull String packageName) { + Application application = Application.GMS_APP; + this.application = Constants.GMS_PACKAGE_NAME.equals(packageName) ? Application.GMS_APP : new Application(packageName); + return this; + } + + /** + * Sets the package name for the application that is recording or computing the data based on the app's context. This method should be + * preferred when an application is creating a data source that represents its own data. When creating a data source to query data from other + * apps, {@link #setAppPackageName(String)} should be used. + */ + @NonNull + public Builder setAppPackageName(@NonNull Context appContext) { + setAppPackageName(appContext.getPackageName()); + return this; + } + + /** + * Sets the data type for the data source. Every data source is required to have a data type. + * + * @param dataType One of the data types defined in {@link DataType}, or a custom data type. + */ + @NonNull + public Builder setDataType(@NonNull DataType dataType) { + this.dataType = dataType; + return this; + } + + /** + * Sets the integrated device where data is being recorded (for instance, a phone that has sensors, or a wearable). Can be useful to identify the + * data source, and to disambiguate between data from different devices. If the data is coming from the local device, use + * {@link Device#getLocalDevice(Context)}. + *

+ * Note that it may be useful to set the device even if the data is not coming from a hardware sensor on the device. For instance, if the user + * installs an application which generates sensor data in two separate devices, the only way to differentiate the two data sources is using the + * device. This can be specially important if both devices are used at the same time. + */ + @NonNull + public Builder setDevice(@NonNull Device device) { + this.device = device; + return this; + } + + /** + * The stream name uniquely identifies this particular data source among other data sources of the same type from the same underlying + * producer. Setting the stream name is optional, but should be done whenever an application exposes two streams for the same data type, or + * when a device has two equivalent sensors. + *

+ * The stream name is used by {@link DataSource#getStreamIdentifier()} to make sure the different streams are properly separated when + * querying or persisting data. + * + * @throws IllegalArgumentException If the specified stream name is null. + */ + @NonNull + public Builder setStreamName(@NonNull String streamName) { + //noinspection ConstantValue + if (streamName == null) throw new IllegalArgumentException("streamName must be set"); + this.streamName = streamName; + return this; + } + + /** + * Sets the type of the data source. {@link DataSource#TYPE_DERIVED} should be used if any other data source is used in generating the data. + * {@link DataSource#TYPE_RAW} should be used if the data comes completely from outside of Google Fit. + */ + @NonNull + public Builder setType(int type) { + this.type = type; + return this; + } + } + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(DataSource.class); } diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataType.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataType.java index 10cbe66472..a66a9dccd6 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataType.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/DataType.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2023 microG Project Team * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.fitness.data; @@ -9,6 +12,7 @@ import androidx.annotation.NonNull; +import androidx.annotation.Nullable; 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; @@ -17,79 +21,178 @@ import java.util.Collections; import java.util.List; +import static com.google.android.gms.fitness.data.Field.*; + +/** + * The data type defines the schema for a stream of data being collected by, inserted into, or queried from Google Fit. The data type defines + * only the representation and format of the data, and not how it's being collected, the sensor being used, or the parameters of the collection. + *

+ * A data type contains one or more fields. In case of multi-dimensional data (such as location with latitude, longitude, and accuracy) each + * field represents one dimension. Each data type field has a unique name which identifies it. The field also defines the format of the data + * (such as int or float). + *

+ * The data types in the {@code com.google} namespace are shared with any app with the user consent. These are fixed and can only be updated in + * new releases of the platform. This class contains constants representing each of the {@code com.google} data types, each prefixed with {@code TYPE_}. + * Custom data types can be accessed via the {@link ConfigClient}. + *

+ * Certain data types can represent aggregates, and can be computed as part of read requests by calling + * {@link DataReadRequest.Builder#aggregate(DataType)}. This class contains constants for all the valid aggregates, each prefixed with + * {@code AGGREGATE_}. The aggregates for each input type can be queried via {@link #getAggregatesForInput(DataType)}. + */ @SafeParcelable.Class public class DataType extends AbstractSafeParcelable { + /** + * The common prefix for data type MIME types, for use in intents. The MIME type for a particular data type will be this prefix followed by + * the data type name. + *

+ * The data type's name is returned by {@link #getName()}. The full MIME type can be computed by {@link #getMimeType(DataType)}. + */ + public static final String MIME_TYPE_PREFIX = "vnd.google.fitness.data_type/"; - public static final DataType TYPE_STEP_COUNT_DELTA = new DataType("com.google.step_count.delta", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_STEPS); - public static final DataType TYPE_STEP_COUNT_CUMULATIVE = new DataType("com.google.step_count.cumulative", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_STEPS); - public static final DataType TYPE_STEP_COUNT_CADENCE = new DataType("com.google.step_count.cadence", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_RPM); - public static final DataType TYPE_INTERNAL_GOAL = new DataType("com.google.internal.goal", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_FITNESS_GOAL_V2); - public static final DataType TYPE_ACTIVITY_SEGMENT = new DataType("com.google.activity.segment", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_ACTIVITY); - public static final DataType TYPE_SLEEP_SEGMENT = new DataType("com.google.sleep.segment", "https://www.googleapis.com/auth/fitness.sleep.read", "https://www.googleapis.com/auth/fitness.sleep.write", com.google.android.gms.fitness.data.Field.FIELD_SLEEP_SEGMENT_TYPE); - public static final DataType TYPE_CALORIES_EXPENDED = new DataType("com.google.calories.expended", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_CALORIES); - public static final DataType TYPE_BASAL_METABOLIC_RATE = new DataType("com.google.calories.bmr", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_CALORIES); - public static final DataType TYPE_POWER_SAMPLE = new DataType("com.google.power.sample", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_WATTS); - public static final DataType TYPE_SENSOR_EVENTS = new DataType("com.google.sensor.events", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_SENSOR_TYPE, com.google.android.gms.fitness.data.Field.FIELD_TIMESTAMPS, com.google.android.gms.fitness.data.Field.FIELD_SENSOR_VALUES); - public static final DataType TYPE_HEART_RATE_BPM = new DataType("com.google.heart_rate.bpm", "https://www.googleapis.com/auth/fitness.heart_rate.read", "https://www.googleapis.com/auth/fitness.heart_rate.write", com.google.android.gms.fitness.data.Field.FIELD_BPM); - public static final DataType TYPE_RESPIRATORY_RATE = new DataType("com.google.respiratory_rate", "https://www.googleapis.com/auth/fitness.respiratory_rate.read", "https://www.googleapis.com/auth/fitness.respiratory_rate.write", com.google.android.gms.fitness.data.Field.FIELD_RESPIRATORY_RATE); - public static final DataType TYPE_LOCATION_SAMPLE = new DataType("com.google.location.sample", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_LATITUDE, com.google.android.gms.fitness.data.Field.FIELD_LONGITUDE, com.google.android.gms.fitness.data.Field.FIELD_ACCURACY, com.google.android.gms.fitness.data.Field.FIELD_ALTITUDE); + public static final DataType TYPE_ACTIVITY_SEGMENT = new DataType("com.google.activity.segment", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_ACTIVITY); + public static final DataType TYPE_BASAL_METABOLIC_RATE = new DataType("com.google.calories.bmr", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_CALORIES); + public static final DataType TYPE_BODY_FAT_PERCENTAGE = new DataType("com.google.body.fat.percentage", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", FIELD_PERCENTAGE); + public static final DataType TYPE_CALORIES_EXPENDED = new DataType("com.google.calories.expended", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_CALORIES); + public static final DataType TYPE_CYCLING_PEDALING_CADENCE = new DataType("com.google.cycling.pedaling.cadence", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_RPM); + public static final DataType TYPE_CYCLING_PEDALING_CUMULATIVE = new DataType("com.google.cycling.pedaling.cumulative", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_REVOLUTIONS); + public static final DataType TYPE_CYCLING_WHEEL_REVOLUTION = new DataType("com.google.cycling.wheel_revolution.cumulative", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_REVOLUTIONS); + public static final DataType TYPE_CYCLING_WHEEL_RPM = new DataType("com.google.cycling.wheel_revolution.rpm", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_RPM); + public static final DataType TYPE_DISTANCE_DELTA = new DataType("com.google.distance.delta", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_DISTANCE); + public static final DataType TYPE_HEART_POINTS = new DataType("com.google.heart_minutes", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_INTENSITY); + public static final DataType TYPE_HEART_RATE_BPM = new DataType("com.google.heart_rate.bpm", "https://www.googleapis.com/auth/fitness.heart_rate.read", "https://www.googleapis.com/auth/fitness.heart_rate.write", FIELD_BPM); + public static final DataType TYPE_HEIGHT = new DataType("com.google.height", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", FIELD_HEIGHT); + public static final DataType TYPE_HYDRATION = new DataType("com.google.hydration", "https://www.googleapis.com/auth/fitness.nutrition.read", "https://www.googleapis.com/auth/fitness.nutrition.write", FIELD_VOLUME); + public static final DataType TYPE_LOCATION_SAMPLE = new DataType("com.google.location.sample", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_LATITUDE, FIELD_LONGITUDE, FIELD_ACCURACY, FIELD_ALTITUDE); @Deprecated - public static final DataType TYPE_LOCATION_TRACK = new DataType("com.google.location.track", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_LATITUDE, com.google.android.gms.fitness.data.Field.FIELD_LONGITUDE, com.google.android.gms.fitness.data.Field.FIELD_ACCURACY, com.google.android.gms.fitness.data.Field.FIELD_ALTITUDE); - public static final DataType TYPE_DISTANCE_DELTA = new DataType("com.google.distance.delta", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_DISTANCE); - public static final DataType TYPE_SPEED = new DataType("com.google.speed", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_SPEED); - public static final DataType TYPE_CYCLING_WHEEL_REVOLUTION = new DataType("com.google.cycling.wheel_revolution.cumulative", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_REVOLUTIONS); - public static final DataType TYPE_CYCLING_WHEEL_RPM = new DataType("com.google.cycling.wheel_revolution.rpm", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_RPM); - public static final DataType TYPE_CYCLING_PEDALING_CUMULATIVE = new DataType("com.google.cycling.pedaling.cumulative", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_REVOLUTIONS); - public static final DataType TYPE_CYCLING_PEDALING_CADENCE = new DataType("com.google.cycling.pedaling.cadence", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_RPM); - public static final DataType TYPE_HEIGHT = new DataType("com.google.height", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", com.google.android.gms.fitness.data.Field.FIELD_HEIGHT); - public static final DataType TYPE_WEIGHT = new DataType("com.google.weight", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", com.google.android.gms.fitness.data.Field.FIELD_WEIGHT); - public static final DataType TYPE_BODY_FAT_PERCENTAGE = new DataType("com.google.body.fat.percentage", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", com.google.android.gms.fitness.data.Field.FIELD_PERCENTAGE); - public static final DataType TYPE_NUTRITION = new DataType("com.google.nutrition", "https://www.googleapis.com/auth/fitness.nutrition.read", "https://www.googleapis.com/auth/fitness.nutrition.write", com.google.android.gms.fitness.data.Field.FIELD_NUTRIENTS, com.google.android.gms.fitness.data.Field.FIELD_MEAL_TYPE, com.google.android.gms.fitness.data.Field.FIELD_FOOD_ITEM); - public static final DataType AGGREGATE_HYDRATION = new DataType("com.google.hydration", "https://www.googleapis.com/auth/fitness.nutrition.read", "https://www.googleapis.com/auth/fitness.nutrition.write", com.google.android.gms.fitness.data.Field.FIELD_VOLUME); - public static final DataType TYPE_WORKOUT_EXERCISE = new DataType("com.google.activity.exercise", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_EXERCISE, com.google.android.gms.fitness.data.Field.FIELD_REPETITIONS, com.google.android.gms.fitness.data.Field.FIELD_DURATION_OPTIONAL, com.google.android.gms.fitness.data.Field.FIELD_RESISTANCE_TYPE, com.google.android.gms.fitness.data.Field.FIELD_RESISTANCE); - public static final DataType TYPE_MOVE_MINUTES = new DataType("com.google.active_minutes", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_DURATION); + public static final DataType TYPE_LOCATION_TRACK = new DataType("com.google.location.track", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_LATITUDE, FIELD_LONGITUDE, FIELD_ACCURACY, FIELD_ALTITUDE); + public static final DataType TYPE_MOVE_MINUTES = new DataType("com.google.active_minutes", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_DURATION); + public static final DataType TYPE_NUTRITION = new DataType("com.google.nutrition", "https://www.googleapis.com/auth/fitness.nutrition.read", "https://www.googleapis.com/auth/fitness.nutrition.write", FIELD_NUTRIENTS, FIELD_MEAL_TYPE, FIELD_FOOD_ITEM); + public static final DataType TYPE_POWER_SAMPLE = new DataType("com.google.power.sample", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_WATTS); + public static final DataType TYPE_SLEEP_SEGMENT = new DataType("com.google.sleep.segment", "https://www.googleapis.com/auth/fitness.sleep.read", "https://www.googleapis.com/auth/fitness.sleep.write", FIELD_SLEEP_SEGMENT_TYPE); + public static final DataType TYPE_SPEED = new DataType("com.google.speed", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_SPEED); + public static final DataType TYPE_STEP_COUNT_CADENCE = new DataType("com.google.step_count.cadence", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_RPM); + public static final DataType TYPE_STEP_COUNT_DELTA = new DataType("com.google.step_count.delta", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_STEPS); + public static final DataType TYPE_WEIGHT = new DataType("com.google.weight", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", FIELD_WEIGHT); + public static final DataType TYPE_WORKOUT_EXERCISE = new DataType("com.google.activity.exercise", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_EXERCISE, FIELD_REPETITIONS, FIELD_DURATION_OPTIONAL, FIELD_RESISTANCE_TYPE, FIELD_RESISTANCE); + + public static final DataType TYPE_DEVICE_ON_BODY = new DataType("com.google.device_on_body", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_PROBABILITY); + public static final DataType TYPE_INTERNAL_GOAL = new DataType("com.google.internal.goal", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_FITNESS_GOAL_V2); + public static final DataType TYPE_MET = new DataType("com.google.internal.met", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_MET); + public static final DataType TYPE_PACED_WALKING_ATTRIBUTES = new DataType("com.google.internal.paced_walking_attributes", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_FITNESS_PACED_WALKING_ATTRIBUTES); + public static final DataType TYPE_RESPIRATORY_RATE = new DataType("com.google.respiratory_rate", "https://www.googleapis.com/auth/fitness.respiratory_rate.read", "https://www.googleapis.com/auth/fitness.respiratory_rate.write", FIELD_RESPIRATORY_RATE); + public static final DataType TYPE_SENSOR_EVENTS = new DataType("com.google.sensor.events", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_SENSOR_TYPE, FIELD_TIMESTAMPS, FIELD_SENSOR_VALUES); + public static final DataType TYPE_SLEEP_ATTRIBUTES = new DataType("com.google.internal.sleep_attributes", "https://www.googleapis.com/auth/fitness.sleep.read", "https://www.googleapis.com/auth/fitness.sleep.write", FIELD_FITNESS_SLEEP_ATTRIBUTES); + public static final DataType TYPE_SLEEP_SCHEDULE = new DataType("com.google.internal.sleep_schedule", "https://www.googleapis.com/auth/fitness.sleep.read", "https://www.googleapis.com/auth/fitness.sleep.write", FIELD_FITNESS_SLEEP_SCHEDULE); + public static final DataType TYPE_STEP_COUNT_CUMULATIVE = new DataType("com.google.step_count.cumulative", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_STEPS); + public static final DataType TYPE_TIME_ZONE_CHANGE = new DataType("com.google.time_zone_change", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_ZONE_ID); + public static final DataType TYPE_WORKOUT_SAMPLES = new DataType("com.google.activity.samples", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_ACTIVITY_CONFIDENCE); + + + public static final DataType AGGREGATE_ACTIVITY_SUMMARY = new DataType("com.google.activity.summary", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_ACTIVITY, FIELD_DURATION, FIELD_NUM_SEGMENTS); + public static final DataType AGGREGATE_BASAL_METABOLIC_RATE_SUMMARY = new DataType("com.google.calories.bmr.summary", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_AVERAGE, FIELD_MAX, FIELD_MIN); + public static final DataType AGGREGATE_BODY_FAT_PERCENTAGE_SUMMARY = new DataType("com.google.body.fat.percentage.summary", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", FIELD_AVERAGE, FIELD_MAX, FIELD_MIN); + public static final DataType AGGREGATE_CALORIES_EXPENDED = TYPE_CALORIES_EXPENDED; + public static final DataType AGGREGATE_DISTANCE_DELTA = TYPE_DISTANCE_DELTA; + public static final DataType AGGREGATE_HEART_POINTS = new DataType("com.google.heart_minutes.summary", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_INTENSITY, FIELD_DURATION); + public static final DataType AGGREGATE_HEART_RATE_SUMMARY = new DataType("com.google.heart_rate.summary", "https://www.googleapis.com/auth/fitness.heart_rate.read", "https://www.googleapis.com/auth/fitness.heart_rate.write", FIELD_AVERAGE, FIELD_MAX, FIELD_MIN); + public static final DataType AGGREGATE_HEIGHT_SUMMARY = new DataType("com.google.height.summary", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", FIELD_AVERAGE, FIELD_MAX, FIELD_MIN); + public static final DataType AGGREGATE_HYDRATION = TYPE_HYDRATION; + public static final DataType AGGREGATE_LOCATION_BOUNDING_BOX = new DataType("com.google.location.bounding_box", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_LOW_LATITUDE, FIELD_LOW_LONGITUDE, FIELD_HIGH_LATITUDE, FIELD_HIGH_LONGITUDE); public static final DataType AGGREGATE_MOVE_MINUTES = TYPE_MOVE_MINUTES; - public static final DataType TYPE_DEVICE_ON_BODY = new DataType("com.google.device_on_body", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_PROBABILITY); - public static final DataType AGGREGATE_ACTIVITY_SUMMARY = new DataType("com.google.activity.summary", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_ACTIVITY, com.google.android.gms.fitness.data.Field.FIELD_DURATION, com.google.android.gms.fitness.data.Field.FIELD_NUM_SEGMENTS); - public static final DataType AGGREGATE_BASAL_METABOLIC_RATE_SUMMARY = new DataType("com.google.calories.bmr.summary", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_AVERAGE, com.google.android.gms.fitness.data.Field.FIELD_MAX, com.google.android.gms.fitness.data.Field.FIELD_MIN); + public static final DataType AGGREGATE_NUTRITION_SUMMARY = new DataType("com.google.nutrition.summary", "https://www.googleapis.com/auth/fitness.nutrition.read", "https://www.googleapis.com/auth/fitness.nutrition.write", FIELD_NUTRIENTS, FIELD_MEAL_TYPE); + public static final DataType AGGREGATE_POWER_SUMMARY = new DataType("com.google.power.summary", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", FIELD_AVERAGE, FIELD_MAX, FIELD_MIN); + public static final DataType AGGREGATE_SPEED_SUMMARY = new DataType("com.google.speed.summary", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", FIELD_AVERAGE, FIELD_MAX, FIELD_MIN); public static final DataType AGGREGATE_STEP_COUNT_DELTA = TYPE_STEP_COUNT_DELTA; - public static final DataType AGGREGATE_DISTANCE_DELTA = TYPE_DISTANCE_DELTA; - public static final DataType AGGREGATE_CALORIES_EXPENDED = TYPE_CALORIES_EXPENDED; - public static final DataType TYPE_HEART_POINTS = new DataType("com.google.heart_minutes", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_INTENSITY); - public static final DataType AGGREGATE_HEART_POINTS = new DataType("com.google.heart_minutes.summary", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_INTENSITY, com.google.android.gms.fitness.data.Field.FIELD_DURATION); - public static final DataType AGGREGATE_HEART_RATE_SUMMARY = new DataType("com.google.heart_rate.summary", "https://www.googleapis.com/auth/fitness.heart_rate.read", "https://www.googleapis.com/auth/fitness.heart_rate.write", com.google.android.gms.fitness.data.Field.FIELD_AVERAGE, com.google.android.gms.fitness.data.Field.FIELD_MAX, com.google.android.gms.fitness.data.Field.FIELD_MIN); - public static final DataType AGGREGATE_LOCATION_BOUNDING_BOX = new DataType("com.google.location.bounding_box", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_LOW_LATITUDE, com.google.android.gms.fitness.data.Field.FIELD_LOW_LONGITUDE, com.google.android.gms.fitness.data.Field.FIELD_HIGH_LATITUDE, com.google.android.gms.fitness.data.Field.FIELD_HIGH_LONGITUDE); - public static final DataType AGGREGATE_POWER_SUMMARY = new DataType("com.google.power.summary", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_AVERAGE, com.google.android.gms.fitness.data.Field.FIELD_MAX, com.google.android.gms.fitness.data.Field.FIELD_MIN); - public static final DataType AGGREGATE_SPEED_SUMMARY = new DataType("com.google.speed.summary", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_AVERAGE, com.google.android.gms.fitness.data.Field.FIELD_MAX, com.google.android.gms.fitness.data.Field.FIELD_MIN); - public static final DataType AGGREGATE_BODY_FAT_PERCENTAGE_SUMMARY = new DataType("com.google.body.fat.percentage.summary", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", com.google.android.gms.fitness.data.Field.FIELD_AVERAGE, com.google.android.gms.fitness.data.Field.FIELD_MAX, com.google.android.gms.fitness.data.Field.FIELD_MIN); - public static final DataType AGGREGATE_WEIGHT_SUMMARY = new DataType("com.google.weight.summary", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", com.google.android.gms.fitness.data.Field.FIELD_AVERAGE, com.google.android.gms.fitness.data.Field.FIELD_MAX, com.google.android.gms.fitness.data.Field.FIELD_MIN); - public static final DataType AGGREGATE_HEIGHT_SUMMARY = new DataType("com.google.height.summary", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", com.google.android.gms.fitness.data.Field.FIELD_AVERAGE, com.google.android.gms.fitness.data.Field.FIELD_MAX, com.google.android.gms.fitness.data.Field.FIELD_MIN); - public static final DataType AGGREGATE_NUTRITION_SUMMARY = new DataType("com.google.nutrition.summary", "https://www.googleapis.com/auth/fitness.nutrition.read", "https://www.googleapis.com/auth/fitness.nutrition.write", com.google.android.gms.fitness.data.Field.FIELD_NUTRIENTS, com.google.android.gms.fitness.data.Field.FIELD_MEAL_TYPE); - public static final DataType TYPE_HYDRATION = AGGREGATE_HYDRATION; - public static final DataType TYPE_WORKOUT_SAMPLES = new DataType("com.google.activity.samples", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_ACTIVITY_CONFIDENCE); - public static final DataType TYPE_SLEEP_ATTRIBUTES = new DataType("com.google.internal.sleep_attributes", "https://www.googleapis.com/auth/fitness.sleep.read", "https://www.googleapis.com/auth/fitness.sleep.write", com.google.android.gms.fitness.data.Field.FIELD_FITNESS_SLEEP_ATTRIBUTES); - public static final DataType TYPE_SLEEP_SCHEDULE = new DataType("com.google.internal.sleep_schedule", "https://www.googleapis.com/auth/fitness.sleep.read", "https://www.googleapis.com/auth/fitness.sleep.write", com.google.android.gms.fitness.data.Field.FIELD_FITNESS_SLEEP_SCHEDULE); - public static final DataType TYPE_PACED_WALKING_ATTRIBUTES = new DataType("com.google.internal.paced_walking_attributes", "https://www.googleapis.com/auth/fitness.activity.read", "https://www.googleapis.com/auth/fitness.activity.write", com.google.android.gms.fitness.data.Field.FIELD_FITNESS_PACED_WALKING_ATTRIBUTES); - public static final DataType TYPE_TIME_ZONE_CHANGE = new DataType("com.google.time_zone_change", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_ZONE_ID); - public static final DataType TYPE_MET = new DataType("com.google.internal.met", "https://www.googleapis.com/auth/fitness.location.read", "https://www.googleapis.com/auth/fitness.location.write", com.google.android.gms.fitness.data.Field.FIELD_MET); - - @Field(1) - public String packageName; - @Field(2) - public List fields; + public static final DataType AGGREGATE_WEIGHT_SUMMARY = new DataType("com.google.weight.summary", "https://www.googleapis.com/auth/fitness.body.read", "https://www.googleapis.com/auth/fitness.body.write", FIELD_AVERAGE, FIELD_MAX, FIELD_MIN); + + @Field(value = 1, getterName = "getName") + @NonNull + private final String name; + @Field(value = 2, getterName = "getFields") + @NonNull + private final List fields; @Field(3) - public String name; + @Nullable + final String readScope; @Field(4) - public String value; + @Nullable + final String writeScope; + + DataType(@NonNull String name, @Nullable String readScope, @Nullable String writeScope, com.google.android.gms.fitness.data.Field... fields) { + this.name = name; + this.readScope = readScope; + this.writeScope = writeScope; + this.fields = Collections.unmodifiableList(Arrays.asList(fields)); + } - public DataType(String packageName, String name, String value, com.google.android.gms.fitness.data.Field... fieldArr) { - this.packageName = packageName; - this.fields = Collections.unmodifiableList(Arrays.asList(fieldArr)); + @Constructor + DataType(@Param(1) @NonNull String name, @Param(2) @NonNull List fields, @Param(3) @Nullable String readScope, @Param(4) @Nullable String writeScope) { this.name = name; - this.value = value; + this.fields = fields; + this.readScope = readScope; + this.writeScope = writeScope; + } + + /** + * Returns the aggregate output type for this type, or {@code null} if the type does not support aggregation. + *

+ * To check if a data type is supported for aggregation, check that the returned type is non-null. + */ + @Nullable + public DataType getAggregateType() { + // TODO + return null; + } + + /** + * Returns the ordered list of fields for the data type. + */ + @NonNull + public List getFields() { + return fields; + } + + /** + * Returns the namespaced name which uniquely identifies this data type. + */ + @NonNull + public String getName() { + return name; + } + + /** + * Returns the index of a field. + * + * @throws IllegalArgumentException If field isn't defined for this data type. + */ + public int indexOf(@NonNull com.google.android.gms.fitness.data.Field field) { + int indexOf = this.fields.indexOf(field); + if (indexOf < 0) throw new IllegalArgumentException("Field not found"); + return indexOf; + } + + /** + * Returns a list of output aggregate data types for the specified {@code inputDataType}. + *

+ * To check if a data type is supported for aggregation, check that the returned list is not empty + * {@code DataType.getAggregatesForInput(dataType).isEmpty()}. + * + * @deprecated Use {@link #getAggregateType()} instead. + */ + @NonNull + @Deprecated + public static List getAggregatesForInput(@NonNull DataType inputDataType) { + DataType aggregateType = inputDataType.getAggregateType(); + if (aggregateType == null) return Collections.emptyList(); + return Collections.singletonList(aggregateType); } - public DataType() { + /** + * Returns the MIME type for a particular {@link DataType}. The MIME type is used in intents such as the data view intent. + */ + @NonNull + public static String getMimeType(@NonNull DataType dataType) { + return MIME_TYPE_PREFIX + dataType.getName(); } @Override diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Device.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Device.java index 558569cde7..846d3b6edd 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Device.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Device.java @@ -1,29 +1,172 @@ /* * SPDX-FileCopyrightText: 2023 microG Project Team * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.fitness.data; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.os.Build; import android.os.Parcel; +import android.provider.Settings; 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 static android.os.Build.VERSION.SDK_INT; + +/** + * Representation of an integrated device (such as a phone or a wearable) that can hold sensors. Each sensor is exposed as a {@link DataSource}. + */ @SafeParcelable.Class public class Device extends AbstractSafeParcelable { - @Field(1) - public String manufacturer; - @Field(2) - public String model; - @Field(4) - public String uid; - @Field(5) - public int type; + /** + * Constant indicating the device type is not known. + */ + public static final int TYPE_UNKNOWN = 0; + /** + * Constant indicating the device is an Android phone. + */ + public static final int TYPE_PHONE = 1; + /** + * Constant indicating the device is an Android tablet. + */ + public static final int TYPE_TABLET = 2; + /** + * Constant indicating the device is a watch or other wrist-mounted band. + */ + public static final int TYPE_WATCH = 3; + /** + * Constant indicating the device is a chest strap. + */ + public static final int TYPE_CHEST_STRAP = 4; + /** + * Constant indicating the device is a scale or similar device the user steps on. + */ + public static final int TYPE_SCALE = 5; + /** + * Constant indicating the device is a headset, pair of glasses, or other head-mounted device + */ + public static final int TYPE_HEAD_MOUNTED = 6; + + @Field(value = 1, getterName = "getManufacturer") + @NonNull + private final String manufacturer; + @Field(value = 2, getterName = "getModel") + @NonNull + private final String model; + @Field(value = 4, getterName = "getUid") + @NonNull + private final String uid; + @Field(value = 5, getterName = "getType") + private final int type; + @Field(value = 6, getterName = "getPlatformType") + private final int platformType; + + @Constructor + Device(@NonNull @Param(1) String manufacturer, @NonNull @Param(2) String model, @NonNull @Param(4) String uid, @Param(5) int type, @Param(6) int platformType) { + this.manufacturer = manufacturer; + this.model = model; + this.uid = uid; + this.type = type; + this.platformType = platformType; + } + + /** + * Creates a new device. + * + * @param manufacturer The manufacturer of the product/hardware. + * @param model The end-user-visible name for the end product. + * @param uid A serial number or other unique identifier for the particular device hardware. + * @param type The type of device. One of the type constants. + */ + public Device(@NonNull String manufacturer, @NonNull String model, @NonNull String uid, int type) { + this(manufacturer, model, uid, type, 0); + } + + /** + * Returns the manufacturer of the product/hardware. + */ + @NonNull + public String getManufacturer() { + return manufacturer; + } + + /** + * Returns the end-user-visible model name for the device. + */ + @NonNull + public String getModel() { + return model; + } + + /** + * Returns the constant representing the type of the device. This will usually be one of the values from the type constants in this class, but it's + * not required to be. Any other value should be treated as {@link #TYPE_UNKNOWN}. + */ + public int getType() { + return type; + } + + /** + * Returns the serial number or other unique ID for the hardware. + *

+ * Device UIDs are obfuscated based on the calling application's package name. Different applications will see different UIDs for the same + * {@link Device}. If two {@link Device} instances have the same underlying UID, they'll also have the same obfuscated UID within each app (but not across + * apps). + */ + @NonNull + public String getUid() { + return uid; + } + + String getDeviceId() { + return manufacturer + ":" + model + ":" + uid; + } + + int getPlatformType() { + return platformType; + } + + /** + * Returns the Device representation of the local device, which can be used when defining local data sources. + * + * @noinspection deprecation + */ + public static Device getLocalDevice(Context context) { + @SuppressLint("HardwareIds") String uid = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); + int type = TYPE_PHONE; + Configuration configuration = context.getResources().getConfiguration(); + PackageManager packageManager = context.getPackageManager(); + if (SDK_INT >= 20 && packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) + type = TYPE_WATCH; + else if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION) || packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) + type = TYPE_UNKNOWN; // TV + else if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) + type = TYPE_UNKNOWN; // Car + else if ((configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) > Configuration.SCREENLAYOUT_SIZE_LARGE && configuration.smallestScreenWidthDp >= 600) + type = TYPE_TABLET; + else if (Build.PRODUCT.startsWith("glass_")) + type = TYPE_HEAD_MOUNTED; + return new Device(Build.MANUFACTURER, Build.MODEL, uid, type, 2); + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("Device").value(getDeviceId() + ":" + type + ":" + platformType).end(); + } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Field.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Field.java index a8b951a1d6..3971ecce5e 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Field.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Field.java @@ -1,6 +1,9 @@ /* * SPDX-FileCopyrightText: 2023 microG Project Team * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.fitness.data; @@ -9,136 +12,471 @@ import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import org.microg.gms.common.Hide; +/** + * A field represents one dimension of a data type. It defines the name and format of data. Unlike data type names, field names are not + * namespaced, and only need to be unique within the data type. + *

+ * This class contains constants representing the field names of common data types, each prefixed with {@code FIELD_}. These can be used to + * access and set the fields via {@link DataPoint#getValue(com.google.android.gms.fitness.data.Field)}. + *

+ * Fields for custom data types can be created using {@link DataTypeCreateRequest.Builder#addField(String, int)}. + */ @SafeParcelable.Class public class Field extends AbstractSafeParcelable { - public static final com.google.android.gms.fitness.data.Field FIELD_ACTIVITY = formatIntField("activity"); - public static final com.google.android.gms.fitness.data.Field FIELD_SLEEP_SEGMENT_TYPE = formatIntField("sleep_segment_type"); - public static final com.google.android.gms.fitness.data.Field FIELD_CONFIDENCE = formatFloatField("confidence"); - public static final com.google.android.gms.fitness.data.Field FIELD_STEPS = formatIntField("steps"); - @Deprecated - public static final com.google.android.gms.fitness.data.Field FIELD_STEP_LENGTH = formatFloatField("step_length"); - public static final com.google.android.gms.fitness.data.Field FIELD_DURATION = formatIntField("duration"); - public static final com.google.android.gms.fitness.data.Field FIELD_DURATION_OPTIONAL = formatIntOptionalField("duration"); - public static final com.google.android.gms.fitness.data.Field FIELD_ACTIVITY_DURATION_ASCENDING = formatMapField("activity_duration.ascending"); - public static final com.google.android.gms.fitness.data.Field FIELD_ACTIVITY_DURATION_DESCENDING = formatMapField("activity_duration.descending"); - public static final com.google.android.gms.fitness.data.Field FIELD_BPM = formatFloatField("bpm"); - public static final com.google.android.gms.fitness.data.Field FIELD_RESPIRATORY_RATE = formatFloatField("respiratory_rate"); - public static final com.google.android.gms.fitness.data.Field FIELD_LATITUDE = formatFloatField("latitude"); - public static final com.google.android.gms.fitness.data.Field FIELD_LONGITUDE = formatFloatField("longitude"); - public static final com.google.android.gms.fitness.data.Field FIELD_ACCURACY = formatFloatField("accuracy"); - public static final com.google.android.gms.fitness.data.Field FIELD_ALTITUDE = formatFloatOptionalField("altitude"); - public static final com.google.android.gms.fitness.data.Field FIELD_DISTANCE = formatFloatField("distance"); - public static final com.google.android.gms.fitness.data.Field FIELD_HEIGHT = formatFloatField("height"); - public static final com.google.android.gms.fitness.data.Field FIELD_WEIGHT = formatFloatField("weight"); - public static final com.google.android.gms.fitness.data.Field FIELD_PERCENTAGE = formatFloatField("percentage"); - public static final com.google.android.gms.fitness.data.Field FIELD_SPEED = formatFloatField("speed"); - public static final com.google.android.gms.fitness.data.Field FIELD_RPM = formatFloatField("rpm"); - public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_GOAL_V2 = formatObjectField("google.android.fitness.GoalV2"); - public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_DEVICE = formatObjectField("google.android.fitness.Device"); - public static final com.google.android.gms.fitness.data.Field FIELD_REVOLUTIONS = formatIntField("revolutions"); - public static final com.google.android.gms.fitness.data.Field FIELD_CALORIES = formatFloatField("calories"); - public static final com.google.android.gms.fitness.data.Field FIELD_WATTS = formatFloatField("watts"); - public static final com.google.android.gms.fitness.data.Field FIELD_VOLUME = formatFloatField("volume"); - public static final com.google.android.gms.fitness.data.Field FIELD_MEAL_TYPE = formatIntOptionalField("meal_type"); - public static final com.google.android.gms.fitness.data.Field FIELD_FOOD_ITEM = formatStringOptionalField("food_item"); - public static final com.google.android.gms.fitness.data.Field FIELD_NUTRIENTS = formatMapField("nutrients"); - public static final com.google.android.gms.fitness.data.Field FIELD_EXERCISE = formatStringField("exercise"); - public static final com.google.android.gms.fitness.data.Field FIELD_REPETITIONS = formatIntOptionalField("repetitions"); - public static final com.google.android.gms.fitness.data.Field FIELD_RESISTANCE = formatFloatOptionalField("resistance"); - public static final com.google.android.gms.fitness.data.Field FIELD_RESISTANCE_TYPE = formatIntOptionalField("resistance_type"); - public static final com.google.android.gms.fitness.data.Field FIELD_NUM_SEGMENTS = formatIntField("num_segments"); - public static final com.google.android.gms.fitness.data.Field FIELD_AVERAGE = formatFloatField("average"); - public static final com.google.android.gms.fitness.data.Field FIELD_MAX = formatFloatField("max"); - public static final com.google.android.gms.fitness.data.Field FIELD_MIN = formatFloatField("min"); - public static final com.google.android.gms.fitness.data.Field FIELD_LOW_LATITUDE = formatFloatField("low_latitude"); - public static final com.google.android.gms.fitness.data.Field FIELD_LOW_LONGITUDE = formatFloatField("low_longitude"); - public static final com.google.android.gms.fitness.data.Field FIELD_HIGH_LATITUDE = formatFloatField("high_latitude"); - public static final com.google.android.gms.fitness.data.Field FIELD_HIGH_LONGITUDE = formatFloatField("high_longitude"); - public static final com.google.android.gms.fitness.data.Field FIELD_OCCURRENCES = formatIntField("occurrences"); - public static final com.google.android.gms.fitness.data.Field FIELD_SENSOR_TYPE = formatIntField("sensor_type"); - public static final com.google.android.gms.fitness.data.Field FIELD_TIMESTAMPS = formatLongField("timestamps"); - public static final com.google.android.gms.fitness.data.Field FIELD_SENSOR_VALUES = formatDoubleField("sensor_values"); - public static final com.google.android.gms.fitness.data.Field FIELD_INTENSITY = formatFloatField("intensity"); - public static final com.google.android.gms.fitness.data.Field FIELD_ACTIVITY_CONFIDENCE = formatMapField("activity_confidence"); - public static final com.google.android.gms.fitness.data.Field FIELD_PROBABILITY = formatFloatField("probability"); - public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_SLEEP_ATTRIBUTES = formatObjectField("google.android.fitness.SleepAttributes"); - public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_SLEEP_SCHEDULE = formatObjectField("google.android.fitness.SleepSchedule"); - @Deprecated - public static final com.google.android.gms.fitness.data.Field FIELD_CIRCUMFERENCE = formatFloatField("circumference"); - public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_PACED_WALKING_ATTRIBUTES = formatObjectField("google.android.fitness.PacedWalkingAttributes"); - public static final com.google.android.gms.fitness.data.Field FIELD_ZONE_ID = formatStringField("zone_id"); - public static final com.google.android.gms.fitness.data.Field FIELD_MET = formatFloatField("met"); - + /** + * Format constant indicating the field holds integer values. + */ public static final int FORMAT_INT32 = 1; + /** + * Format constant indicating the field holds float values. + */ public static final int FORMAT_FLOAT = 2; + /** + * Format constant indicating the field holds string values. Strings should be kept small whenever possible. Data streams with large string + * values and high data frequency may be down sampled. + */ public static final int FORMAT_STRING = 3; + /** + * Format constant indicating the field holds a map of string keys to values. The valid key space and units for the corresponding value should + * be documented as part of the data type definition. + *

+ * Map values can be set using {@link DataPoint.Builder#setField(com.google.android.gms.fitness.data.Field, java.util.Map)}. + *

+ * Keys should be kept small whenever possible. Data streams with large keys and high data frequency may be down sampled. + */ public static final int FORMAT_MAP = 4; + public static final int FORMAT_LONG = 5; public static final int FORMAT_DOUBLE = 6; public static final int FORMAT_OBJECT = 7; - @Field(1) - public String name; - @Field(2) - public int format; - @Field(3) - public Boolean optional; + /** + * Meal type constant representing that the meal type is unknown. + */ + public static final int MEAL_TYPE_UNKNOWN = 0; + /** + * Meal type constant representing a breakfast meal. + */ + public static final int MEAL_TYPE_BREAKFAST = 1; + /** + * Meal type constant representing a lunch meal. + */ + public static final int MEAL_TYPE_LUNCH = 2; + /** + * Meal type constant representing a dinner meal. + */ + public static final int MEAL_TYPE_DINNER = 3; + /** + * Meal type constant representing a snack meal. + */ + public static final int MEAL_TYPE_SNACK = 4; + /** + * Calcium amount in milligrams. + */ + @NonNull + public static final String NUTRIENT_CALCIUM = "calcium"; + /** + * Calories in kcal. + */ + @NonNull + public static final String NUTRIENT_CALORIES = "calories"; + /** + * Cholesterol in milligrams. + */ + @NonNull + public static final String NUTRIENT_CHOLESTEROL = "cholesterol"; + /** + * Dietary fiber in grams. + */ + @NonNull + public static final String NUTRIENT_DIETARY_FIBER = "dietary_fiber"; + /** + * Iron amount in milligrams. + */ + @NonNull + public static final String NUTRIENT_IRON = "iron"; + /** + * Monounsaturated fat in grams. + */ + @NonNull + public static final String NUTRIENT_MONOUNSATURATED_FAT = "fat.monounsaturated"; + /** + * Polyunsaturated fat in grams. + */ + @NonNull + public static final String NUTRIENT_POLYUNSATURATED_FAT = "fat.polyunsaturated"; + /** + * Potassium in milligrams. + */ + @NonNull + public static final String NUTRIENT_POTASSIUM = "potassium"; + /** + * Protein amount in grams. + */ + @NonNull + public static final String NUTRIENT_PROTEIN = "protein"; + /** + * Saturated fat in grams. + */ + @NonNull + public static final String NUTRIENT_SATURATED_FAT = "fat.saturated"; + /** + * Sodium in milligrams. + */ + @NonNull + public static final String NUTRIENT_SODIUM = "sodium"; + /** + * Sugar amount in grams. + */ + @NonNull + public static final String NUTRIENT_SUGAR = "sugar"; + /** + * Total carbohydrates in grams. + */ + @NonNull + public static final String NUTRIENT_TOTAL_CARBS = "carbs.total"; + /** + * Total fat in grams. + */ + @NonNull + public static final String NUTRIENT_TOTAL_FAT = "fat.total"; + /** + * Trans fat in grams. + */ + @NonNull + public static final String NUTRIENT_TRANS_FAT = "fat.trans"; + /** + * Unsaturated fat in grams. + */ + @NonNull + public static final String NUTRIENT_UNSATURATED_FAT = "fat.unsaturated"; + /** + * Vitamin A amount in International Units (IU). For converting from daily percentages, the FDA recommended 5000 IUs Daily Value can be + * used. + */ + @NonNull + public static final String NUTRIENT_VITAMIN_A = "vitamin_a"; + /** + * Vitamin C amount in milligrams. + */ + @NonNull + public static final String NUTRIENT_VITAMIN_C = "vitamin_c"; + /** + * The resistance type is unknown, unspecified, or not represented by any canonical values. + */ + public static final int RESISTANCE_TYPE_UNKNOWN = 0; + /** + * The user is using a barbell for resistance. The specified resistance should include the weight of the bar, as well as weights added to both + * sides. + */ + public static final int RESISTANCE_TYPE_BARBELL = 1; + /** + * The user is using a cable for resistance. When two cables are being used (one for each arm), the specified resistance should include the + * weight being pulled by one cable. + */ + public static final int RESISTANCE_TYPE_CABLE = 2; + /** + * The user is using dumbells for resistance. The specified resistance should include the weight of a single dumbell. + */ + public static final int RESISTANCE_TYPE_DUMBBELL = 3; + /** + * The user is using a kettlebell for resistance. + */ + public static final int RESISTANCE_TYPE_KETTLEBELL = 4; + /** + * The user is performing the exercise in a machine. The specified resistance should match the weight specified by the machine. + */ + public static final int RESISTANCE_TYPE_MACHINE = 5; + /** + * The user is using their own body weight for resistance. + */ + public static final int RESISTANCE_TYPE_BODY = 6; + /** + * The accuracy of an accompanied value (such as location). + */ + public static final com.google.android.gms.fitness.data.Field FIELD_ACCURACY = createFloatField("accuracy"); + /** + * An activity type of {@link FitnessActivities}, encoded as an integer for efficiency. The activity value should be stored using + * {@link DataPoint.Builder#setActivityField(Field, String)}. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_ACTIVITY = createIntField("activity"); + /** + * An altitude of a location represented as a float, in meters above sea level. Some location samples don't have an altitude value so this field + * might not be set. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_ALTITUDE = createOptionalFloatField("altitude"); + /** + * An average value. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_AVERAGE = createFloatField("average"); + /** + * A heart rate in beats per minute. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_BPM = createFloatField("bpm"); + /** + * Calories in kcal. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_CALORIES = createFloatField("calories"); + /** + * Circumference of a body part, in centimeters. + * + * @deprecated There is no applicable replacement field. + */ + @Deprecated + public static final com.google.android.gms.fitness.data.Field FIELD_CIRCUMFERENCE = createFloatField("circumference"); + /** + * The confidence of an accompanied value, specified as a value between 0.0 and 100.0. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_CONFIDENCE = createFloatField("confidence"); + /** + * A distance in meters. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_DISTANCE = createFloatField("distance"); + /** + * A field containing duration. The units of the field are defined by the outer data type. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_DURATION = createIntField("duration"); + /** + * A workout exercise, as represented by one of the constants in {@link WorkoutExercises}. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_EXERCISE = createStringField("exercise"); + /** + * The corresponding food item for a nutrition entry. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_FOOD_ITEM = createOptionalStringField("food_item"); + /** + * A height in meters. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_HEIGHT = createFloatField("height"); + /** + * A high latitude of a location bounding box represented as a float, in degrees. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_HIGH_LATITUDE = createFloatField("high_latitude"); + /** + * A high longitude of a location bounding box represented as a float, in degrees. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_HIGH_LONGITUDE = createFloatField("high_longitude"); + /** + * Intensity of user activity, represented as a float. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_INTENSITY = createFloatField("intensity"); + /** + * A latitude of a location represented as a float, in degrees. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_LATITUDE = createFloatField("latitude"); + /** + * A longitude of a location represented as a float, in degrees. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_LONGITUDE = createFloatField("longitude"); + /** + * A low latitude of a location bounding box represented as a float, in degrees. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_LOW_LATITUDE = createFloatField("low_latitude"); + /** + * A low longitude of a location bounding box represented as a float, in degrees. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_LOW_LONGITUDE = createFloatField("low_longitude"); + /** + * A maximum value. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_MAX = createFloatField("max"); + /** + * A maximum int value. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_MAX_INT = createIntField("max"); + /** + * Type of meal, represented as the appropriate int constant. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_MEAL_TYPE = createOptionalIntField("meal_type"); + /** + * A minimum value. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_MIN = createFloatField("min"); + /** + * A minimum int value. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_MIN_INT = createIntField("min"); + /** + * A number of segments. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_NUM_SEGMENTS = createIntField("num_segments"); + /** + * Nutrients ingested by the user, represented as a float map of nutrient key to quantity. The valid keys of the map are listed in this class using + * the {@code NUTRIENT_} prefix. The documentation for each key describes the unit of its value. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_NUTRIENTS = createMapField("nutrients"); + /** + * How many occurrences of an event there were in a time range. For sample data types this should not be set to more than one. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_OCCURRENCES = createIntField("occurrences"); + /** + * A percentage value, between 0 and 100. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_PERCENTAGE = createFloatField("percentage"); + /** + * A count of repetitions for a single set of a workout exercise. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_REPETITIONS = createOptionalIntField("repetitions"); + /** + * The resistance of the exercise (or weight), in kg. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_RESISTANCE = createOptionalFloatField("resistance"); + /** + * The type of resistance used in this exercise, represented as the appropriate int constant. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_RESISTANCE_TYPE = createOptionalIntField("resistance_type"); + /** + * A count of revolutions. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_REVOLUTIONS = createIntField("revolutions"); + /** + * Revolutions per minute or rate per minute. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_RPM = createFloatField("rpm"); + /** + * Sleep Segment type defined in {@link SleepStages}. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_SLEEP_SEGMENT_TYPE = createIntField("sleep_segment_type"); + /** + * A speed in meter/sec. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_SPEED = createFloatField("speed"); + /** + * A count of steps. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_STEPS = createIntField("steps"); + /** + * Distance between steps in meters. + * + * @deprecated There is no applicable replacement field. + */ + @Deprecated + public static final com.google.android.gms.fitness.data.Field FIELD_STEP_LENGTH = createFloatField("step_length"); + /** + * Volume in liters. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_VOLUME = createFloatField("volume"); + /** + * Power in watts. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_WATTS = createFloatField("watts"); + /** + * A weight in kilograms. + */ + public static final com.google.android.gms.fitness.data.Field FIELD_WEIGHT = createFloatField("weight"); - public Field() { - } + public static final com.google.android.gms.fitness.data.Field FIELD_ACTIVITY_CONFIDENCE = createMapField("activity_confidence"); + public static final com.google.android.gms.fitness.data.Field FIELD_ACTIVITY_DURATION_ASCENDING = createMapField("activity_duration.ascending"); + public static final com.google.android.gms.fitness.data.Field FIELD_ACTIVITY_DURATION_DESCENDING = createMapField("activity_duration.descending"); + public static final com.google.android.gms.fitness.data.Field FIELD_DURATION_OPTIONAL = createOptionalIntField("duration"); + public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_DEVICE = createObjectField("google.android.fitness.Device"); + public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_GOAL_V2 = createObjectField("google.android.fitness.GoalV2"); + public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_SLEEP_ATTRIBUTES = createObjectField("google.android.fitness.SleepAttributes"); + public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_SLEEP_SCHEDULE = createObjectField("google.android.fitness.SleepSchedule"); + public static final com.google.android.gms.fitness.data.Field FIELD_FITNESS_PACED_WALKING_ATTRIBUTES = createObjectField("google.android.fitness.PacedWalkingAttributes"); + public static final com.google.android.gms.fitness.data.Field FIELD_MET = createFloatField("met"); + public static final com.google.android.gms.fitness.data.Field FIELD_PROBABILITY = createFloatField("probability"); + public static final com.google.android.gms.fitness.data.Field FIELD_RESPIRATORY_RATE = createFloatField("respiratory_rate"); + public static final com.google.android.gms.fitness.data.Field FIELD_SENSOR_TYPE = createIntField("sensor_type"); + public static final com.google.android.gms.fitness.data.Field FIELD_SENSOR_VALUES = createDoubleField("sensor_values"); + public static final com.google.android.gms.fitness.data.Field FIELD_TIMESTAMPS = createLongField("timestamps"); + public static final com.google.android.gms.fitness.data.Field FIELD_ZONE_ID = createStringField("zone_id"); + + @Field(value = 1, getterName = "getName") + @NonNull + private final String name; + @Field(value = 2, getterName = "getFormat") + private final int format; + @Field(value = 3, getterName = "isOptional") + @Nullable + private final Boolean optional; - public Field(String name, int format, Boolean optional) { + @Constructor + public Field(@Param(1) @NonNull String name, @Param(2) int format, @Param(3) @Nullable Boolean optional) { this.name = name; this.format = format; this.optional = optional; } - public Field(String name, int format) { + public Field(@NonNull String name, int format) { this(name, format, null); } - public static com.google.android.gms.fitness.data.Field formatIntField(String name) { + /** + * Returns the format of the field, as one of the format constant values. + */ + public int getFormat() { + return format; + } + + /** + * Returns the name of the field. + */ + public String getName() { + return name; + } + + public Boolean isOptional() { + return optional; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof com.google.android.gms.fitness.data.Field)) return false; + + com.google.android.gms.fitness.data.Field field = (com.google.android.gms.fitness.data.Field) o; + return format == field.format && name.equals(field.name); + } + + public static com.google.android.gms.fitness.data.Field createIntField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_INT32); } - public static com.google.android.gms.fitness.data.Field formatFloatField(String name) { + public static com.google.android.gms.fitness.data.Field createFloatField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_FLOAT); } - public static com.google.android.gms.fitness.data.Field formatStringField(String name) { + public static com.google.android.gms.fitness.data.Field createStringField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_STRING); } - public static com.google.android.gms.fitness.data.Field formatMapField(String name) { + public static com.google.android.gms.fitness.data.Field createMapField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_MAP); } - public static com.google.android.gms.fitness.data.Field formatLongField(String name) { + public static com.google.android.gms.fitness.data.Field createLongField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_LONG); } - public static com.google.android.gms.fitness.data.Field formatDoubleField(String name) { + public static com.google.android.gms.fitness.data.Field createDoubleField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_DOUBLE); } - public static com.google.android.gms.fitness.data.Field formatObjectField(String name) { + public static com.google.android.gms.fitness.data.Field createObjectField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_OBJECT); } - public static com.google.android.gms.fitness.data.Field formatIntOptionalField(String name) { + public static com.google.android.gms.fitness.data.Field createOptionalIntField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_INT32, true); } - public static com.google.android.gms.fitness.data.Field formatFloatOptionalField(String name) { + public static com.google.android.gms.fitness.data.Field createOptionalFloatField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_FLOAT, true); } - public static com.google.android.gms.fitness.data.Field formatStringOptionalField(String name) { + public static com.google.android.gms.fitness.data.Field createOptionalStringField(String name) { return new com.google.android.gms.fitness.data.Field(name, FORMAT_STRING, true); } diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/MapValue.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/MapValue.java new file mode 100644 index 0000000000..f03711f100 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/MapValue.java @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.data; + +import android.os.Parcel; +import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import org.microg.gms.common.Hide; + +import static com.google.android.gms.fitness.data.Field.FORMAT_FLOAT; + +@Hide +@SafeParcelable.Class +public class MapValue extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getFormat") + private final int format; + @Field(value = 2, getterName = "getValue") + private final float value; + + @Constructor + public MapValue(@Param(1) int format, @Param(2) float value) { + this.format = format; + this.value = value; + } + + @NonNull + public static MapValue ofFloat(float value) { + return new MapValue(FORMAT_FLOAT, value); + } + + public int getFormat() { + return format; + } + + float getValue() { + return value; + } + + public float asFloat() { + if (format != FORMAT_FLOAT) throw new IllegalStateException("MapValue is not a float"); + return value; + } + + @Override + public int hashCode() { + return (int) value; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(MapValue.class); +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Session.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Session.java index 33132cb9b3..8082ca3435 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Session.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Session.java @@ -1,20 +1,208 @@ /* * SPDX-FileCopyrightText: 2023 microG Project Team * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.fitness.data; +import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Parcel; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer; +import java.util.concurrent.TimeUnit; + +/** + * A Session represents a time interval with associated metadata. Sessions provide a mechanism to store user-visible groups of related + * stream data in a useful and shareable manner, and allows for easy querying of the data in a detailed or aggregated fashion. The start and + * end times for sessions will be controlled by applications, and can be used to represent user-friendly groupings of activities, such as "bike + * ride", "marathon training run". Any data in Google Fit which falls within this time range is implicitly associated with the session. + */ @SafeParcelable.Class public class Session extends AbstractSafeParcelable { + + /** + * Name for the parcelable intent extra containing a session. It can be extracted using {@link #extract(Intent)}. + */ + @NonNull + public static final String EXTRA_SESSION = "vnd.google.fitness.session"; + + /** + * The common prefix for session MIME types. The MIME type for a particular session will be this prefix followed by the session's activity + * name. + *

+ * The session's activity type is returned by {@link #getActivity()}. The MIME type can be computed from the activity using {@link #getMimeType(String)} + */ + @NonNull + public static final String MIME_TYPE_PREFIX = "vnd.google.fitness.session/"; + + @Field(value = 1, getterName = "getStartTimeMillis") + private final long startTimeMillis; + @Field(value = 2, getterName = "getEndTimeMillis") + private final long endTimeMillis; + @Field(value = 3, getterName = "getName") + @Nullable + private final String name; + @Field(value = 4, getterName = "getIdentifier") + @NonNull + private final String identifier; + @Field(value = 5, getterName = "getDescription") + @NonNull + private final String description; + @Field(value = 7, getterName = "getActivityType") + private final int activityType; + @Field(value = 8, getterName = "getApplication") + private final Application application; + @Field(value = 9, getterName = "getActiveTimeMillis") + @Nullable + private final Long activeTimeMillis; + + @Constructor + Session(@Param(1) long startTimeMillis, @Param(2) long endTimeMillis, @Param(3) @Nullable String name, @Param(4) @NonNull String identifier, @Param(5) @NonNull String description, @Param(7) int activityType, @Param(8) Application application, @Param(9) @Nullable Long activeTimeMillis) { + this.startTimeMillis = startTimeMillis; + this.endTimeMillis = endTimeMillis; + this.name = name; + this.identifier = identifier; + this.description = description; + this.activityType = activityType; + this.application = application; + this.activeTimeMillis = activeTimeMillis; + } + + /** + * Returns the active time period of the session. + *

+ * Make sure to use {@link #hasActiveTime()} before using this method. + * + * @throws IllegalStateException {@link #hasActiveTime()} returns false. + */ + public long getActiveTime(@NonNull TimeUnit timeUnit) { + if (activeTimeMillis == null) throw new IllegalStateException("Active time is not set"); + return timeUnit.convert(activeTimeMillis, TimeUnit.MILLISECONDS); + } + + /** + * Returns the activity associated with this session, if set. Else returns {@link FitnessActivities#UNKNOWN}. + */ + @NonNull + public String getActivity() { + return null; // TODO + } + + /** + * Returns the package name for the application responsible for adding the session. or {@code null} if unset/unknown. The {@link PackageManager} can be + * used to query relevant data on the application, such as the name, icon, or logo. + */ + @Nullable + public String getAppPackageName() { + if (application == null) return null; + return application.getPackageName(); + } + + /** + * Returns the description for this session. + */ + @NonNull + public String getDescription() { + return description; + } + + /** + * Returns the end time for the session, in the given unit since epoch. If the session is ongoing (it hasn't ended yet), this will return 0. + */ + public long getEndTime(@NonNull TimeUnit timeUnit) { + return timeUnit.convert(endTimeMillis, TimeUnit.MILLISECONDS); + } + + /** + * Returns the identifier for this session. + */ + @NonNull + public String getIdentifier() { + return identifier; + } + + /** + * Returns the name for this session, if set. + */ + @Nullable + public String getName() { + return name; + } + + /** + * Returns the start time for the session, in the given time unit since epoch. A valid start time is always set. + */ + public long getStartTime(@NonNull TimeUnit timeUnit) { + return timeUnit.convert(startTimeMillis, TimeUnit.MILLISECONDS); + } + + /** + * Returns whether the session active time is set. + */ + public boolean hasActiveTime() { + return activeTimeMillis != null; + } + + /** + * Returns whether the session is ongoing. If the session has ended, this will return false. + */ + public boolean isOngoing() { + return endTimeMillis == 0; + } + + Application getApplication() { + return application; + } + + int getActivityType() { + return activityType; + } + + long getStartTimeMillis() { + return startTimeMillis; + } + + long getEndTimeMillis() { + return endTimeMillis; + } + + @Nullable + Long getActiveTimeMillis() { + return activeTimeMillis; + } + + /** + * Extracts the session extra from the given intent, such as a callback intent received after registering to session start/end notifications, or an intent to view a session. + * + * @param intent The extracted Session, or {@code null} if the given intent does not contain a Session. + */ + @Nullable + public static Session extract(@NonNull Intent intent) { + return SafeParcelableSerializer.deserializeFromBytes(intent.getByteArrayExtra(EXTRA_SESSION), CREATOR); + } + + /** + * Returns the MIME type which describes a Session for a particular activity. The MIME type is used in intents such as the session view + * intent. + * + * @param activity One of the activities in {@link FitnessActivities}. + */ + @NonNull + public static String getMimeType(@NonNull String activity) { + return MIME_TYPE_PREFIX + activity; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/SessionDataSet.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/SessionDataSet.java new file mode 100644 index 0000000000..7037956f24 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/SessionDataSet.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.data; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import org.microg.gms.common.Hide; + +@SafeParcelable.Class +@Hide +public class SessionDataSet extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionDataSet.class); + + @Field(1) + public final Session session; + @Field(2) + public final DataSet dataSet; + + @Constructor + public SessionDataSet(@Param(1) Session session, @Param(2) DataSet dataSet) { + this.session = session; + this.dataSet = dataSet; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Value.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Value.java new file mode 100644 index 0000000000..357a462228 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/data/Value.java @@ -0,0 +1,270 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fitness.data; + +import android.os.Bundle; +import android.os.Build.VERSION; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import androidx.annotation.Nullable; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.util.Map; + +import static com.google.android.gms.fitness.data.Field.*; + +/** + * Holder object for the value of a single field in a data point. Values are not constructed directly; a value for each field of the data type + * is created for each data point. + *

+ * A field value has a particular format, and should be set and read using the format-specific methods. For instance, a float value should be set + * via {@link #setFloat(float)} and read via {@link #asFloat()}. Formats are defined as constants in {@link com.google.android.gms.fitness.data.Field}. + */ +@SafeParcelable.Class +public final class Value extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getFormat") + public int format; + @Field(value = 2, getterName = "isSet") + public boolean set; + @Field(value = 3, getterName = "getValue") + public float value; + @Field(value = 4, getterName = "getStringValue") + public String stringValue; + @Field(value = 5, getterName = "getMapValue") + @Nullable + public Bundle mapValue; + @Field(value = 6, getterName = "getIntArrayValue") + public int[] intArrayValue; + @Field(value = 7, getterName = "getFloatArrayValue") + public float[] floatArrayValue; + @Field(value = 8, getterName = "getBlob") + public byte[] blob; + + @Constructor + public Value(@Param(1) int format, @Param(2) boolean set, @Param(3) float value, @Param(4) String stringValue, @Param(5) @Nullable Bundle mapValue, @Param(6) int[] intArrayValue, @Param(7) float[] floatArrayValue, @Param(8) byte[] blob) { + this.format = format; + this.set = set; + this.value = value; + this.stringValue = stringValue; + this.mapValue = mapValue; + this.intArrayValue = intArrayValue; + this.floatArrayValue = floatArrayValue; + this.blob = blob; + } + + Value(int format) { + this(format, false, 0f, null, null, null, null, null); + } + + /** + * Returns the value of this object as an activity. The integer representation of the activity is converted to a String prior to returning. + * + * @return One of the constants from {@link FitnessActivities}; {@link FitnessActivities#UNKNOWN} if the object does not hold a valid activity + * representation + * @throws IllegalStateException If this {@link Value} does not correspond to a {@link com.google.android.gms.fitness.data.Field#FORMAT_INT32} + */ + public String asActivity() { + return null; // TODO + } + + /** + * Returns the value of this object as a float. + * + * @throws IllegalStateException If this {@link Value} does not correspond to a {@link com.google.android.gms.fitness.data.Field#FORMAT_FLOAT} + */ + public float asFloat() { + if (format != FORMAT_FLOAT) throw new IllegalStateException("Value is not a float."); + return value; + } + + /** + * Returns the value of this object as a int. + * + * @throws IllegalStateException If this {@link Value} does not correspond to a {@link com.google.android.gms.fitness.data.Field#FORMAT_INT32} + */ + public int asInt() { + if (format != FORMAT_INT32) throw new IllegalStateException("Value is not a int."); + return Float.floatToRawIntBits(this.value); + } + + /** + * Returns the value of this object as a string. + * + * @throws IllegalStateException If this {@link Value} does not correspond to a {@link com.google.android.gms.fitness.data.Field#FORMAT_STRING} + */ + @NonNull + public String asString() { + if (format != FORMAT_STRING) throw new IllegalStateException("Value is not a string."); + if (stringValue == null) return ""; + return stringValue; + } + + /** + * Clears any value currently associated with the given {@code key} in the map. This method can be used only on map values. + * + * @param key The key you're modifying. + * @deprecated Use {@link DataPoint.Builder} to construct new {@link DataPoint} instances. + */ + @Deprecated + public void clearKey(String key) { + if (format != FORMAT_MAP) throw new IllegalStateException("Value is not a map."); + if (mapValue != null) { + mapValue.remove(key); + } + } + + /** + * Returns the format of this value, which matches the appropriate field in the data type definition. + * + * @return One of the format constants from {@link com.google.android.gms.fitness.data.Field}. + */ + public int getFormat() { + return format; + } + + /** + * Returns the value of the given key in the map as a {@link Float}. + * + * @return {@code null} if the key doesn't have a set value in the map. + * @throws IllegalStateException If this {@link Value} does not correspond to a {@link com.google.android.gms.fitness.data.Field#FORMAT_MAP} + */ + @Nullable + public Float getKeyValue(@NonNull String key) { + if (format != FORMAT_MAP) throw new IllegalStateException("Value is not a map."); + if (mapValue == null || !mapValue.containsKey(key)) { + return null; + } + mapValue.setClassLoader(MapValue.class.getClassLoader()); + if (VERSION.SDK_INT >= 33) { + return mapValue.getParcelable(key, MapValue.class).asFloat(); + } else { + //noinspection deprecation + return ((MapValue) mapValue.getParcelable(key)).asFloat(); + } + } + + /** + * Returns {@code true} if this object's value has been set by calling one of the setters. + */ + public boolean isSet() { + return set; + } + + float getValue() { + return value; + } + + String getStringValue() { + return stringValue; + } + + @Nullable + Bundle getMapValue() { + return mapValue; + } + + int[] getIntArrayValue() { + return intArrayValue; + } + + float[] getFloatArrayValue() { + return floatArrayValue; + } + + byte[] getBlob() { + return blob; + } + + /** + * Updates this value object to represent an activity value. Activities are internally represented as integers for storage. + * + * @param activity One of the activities from {@link FitnessActivities} + * @deprecated Use {@link DataPoint.Builder} to construct new {@link DataPoint} instances. + */ + public void setActivity(String activity) { + setInt(0); // TODO + } + + /** + * Updates this value object to represent a float value. Any previous values associated with this object are erased. + * + * @param value The new value that this objects holds. + * @deprecated Use {@link DataPoint.Builder} to construct new {@link DataPoint} instances. + */ + public void setFloat(float value) { + if (format != FORMAT_FLOAT) throw new IllegalStateException("Value is not a float."); + this.set = true; + this.value = value; + } + + /** + * Updates this value object to represent an int value. Any previous values are erased. + * + * @param value The new value that this object holds. + * @deprecated Use {@link DataPoint.Builder} to construct new {@link DataPoint} instances. + */ + public void setInt(int value) { + if (format != FORMAT_INT32) throw new IllegalStateException("Value is not a int."); + this.set = true; + this.value = Float.intBitsToFloat(value); + } + + /** + * Updates the value for a given key in the map to the given float value. Any previous values associated with the key are erased. This method + * can be used only on map values. + *

+ * Key values should be kept small whenever possible. This is specially important for high frequency streams, since large keys may result in + * down sampling. + * + * @param key The key you're modifying. + * @param value The new value for the given key. + * @deprecated Use {@link DataPoint.Builder} to construct new {@link DataPoint} instances. + */ + public void setKeyValue(String key, float value) { + if (format != FORMAT_MAP) throw new IllegalStateException("Value is not a map."); + this.set = true; + if (mapValue == null) mapValue = new Bundle(); + mapValue.putParcelable(key, MapValue.ofFloat(value)); + } + + void setMap(@NonNull Map value) { + if (format != FORMAT_MAP) throw new IllegalStateException("Value is not a map."); + this.set = true; + if (mapValue == null) mapValue = new Bundle(); + for (String key : value.keySet()) { + mapValue.putParcelable(key, MapValue.ofFloat(value.get(key))); + } + } + + /** + * Updates this value object to represent a string value. Any previous values associated with this object are erased. + *

+ * String values should be kept small whenever possible. This is specially important for high frequency streams, since large values may result + * in down sampling. + * + * @param value The new value that this objects holds. + * @deprecated Use {@link DataPoint.Builder} to construct new {@link DataPoint} instances. + */ + public void setString(String value) { + if (format != FORMAT_STRING) throw new IllegalStateException("Value is not a string."); + this.set = true; + this.stringValue = value; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(Value.class); +} \ No newline at end of file diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/DataTypeCreateRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/DataTypeCreateRequest.java new file mode 100644 index 0000000000..21902cadec --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/DataTypeCreateRequest.java @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.internal.IDataTypeCallback; + +import java.util.List; + +@SafeParcelable.Class +public class DataTypeCreateRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(DataTypeCreateRequest.class); + + @Field(1) + public String name; + @Field(2) + public List fields; + @Field(3) + public IDataTypeCallback callback; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/DisableFitRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/DisableFitRequest.java new file mode 100644 index 0000000000..5034ea7997 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/DisableFitRequest.java @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.internal.IStatusCallback; + +@SafeParcelable.Class +public class DisableFitRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(DisableFitRequest.class); + + @Field(1) + public IStatusCallback callback; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/ReadDataTypeRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/ReadDataTypeRequest.java new file mode 100644 index 0000000000..631f28d6e1 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/ReadDataTypeRequest.java @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.internal.IDataTypeCallback; + +@SafeParcelable.Class +public class ReadDataTypeRequest extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(ReadDataTypeRequest.class); + + @Field(1) + public String name; + @Field(3) + public IDataTypeCallback callback; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionInsertRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionInsertRequest.java new file mode 100644 index 0000000000..1ca56f2695 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionInsertRequest.java @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.data.DataPoint; +import com.google.android.gms.fitness.data.DataSet; +import com.google.android.gms.fitness.data.Session; +import com.google.android.gms.fitness.internal.IStatusCallback; + +import java.util.List; + +@SafeParcelable.Class +public class SessionInsertRequest extends AbstractSafeParcelable { + + @Field(1) + public Session seesion; + @Field(2) + public List dataSets; + + @Field(3) + public List aggregateDataPoints; + + @Field(4) + public IStatusCallback callback; + + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionInsertRequest.class); + +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionReadRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionReadRequest.java new file mode 100644 index 0000000000..0cbaeb59f4 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionReadRequest.java @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.data.DataSource; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.internal.ISessionReadCallback; + +import java.util.List; + +@SafeParcelable.Class +public class SessionReadRequest extends AbstractSafeParcelable { + @Field(1) + public String sessionName; + @Field(2) + public String sessionId; + @Field(3) + public long StartTimeMillis; + @Field(4) + public long EndTimeMillis; + @Field(5) + public List dataTypes; + @Field(6) + public List dataSources; + @Field(7) + public boolean includeSessionsFromAllApps; + @Field(8) + public boolean areServerQueriesEnabled; + @Field(9) + public List excludedPackages; + @Field(10) + public ISessionReadCallback callback; + @Field(value = 12, defaultValue = "true") + public boolean areActivitySessionsIncluded; + @Field(value = 13, defaultValue = "false") + public boolean areSleepSessionsIncluded; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionReadRequest.class); +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionRegistrationRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionRegistrationRequest.java new file mode 100644 index 0000000000..e12a001b8f --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionRegistrationRequest.java @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.app.PendingIntent; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.api.internal.IStatusCallback; +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 SessionRegistrationRequest extends AbstractSafeParcelable { + + @Field(1) + public PendingIntent intent; + @Field(2) + public IStatusCallback callback; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionRegistrationRequest.class); +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionStartRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionStartRequest.java new file mode 100644 index 0000000000..5d8fddf34f --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionStartRequest.java @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.data.Session; +import com.google.android.gms.fitness.internal.IStatusCallback; + +@SafeParcelable.Class +public class SessionStartRequest extends AbstractSafeParcelable { + @Field(1) + public Session session; + @Field(2) + public IStatusCallback callback; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionStartRequest.class); + +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionStopRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionStopRequest.java new file mode 100644 index 0000000000..729b0bcfaf --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionStopRequest.java @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.internal.ISessionStopCallback; + +@SafeParcelable.Class +public class SessionStopRequest extends AbstractSafeParcelable { + @Field(1) + public String name; + @Field(2) + public String identifier; + @Field(3) + public ISessionStopCallback callback; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionStopRequest.class); + +} \ No newline at end of file diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionUnregistrationRequest.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionUnregistrationRequest.java new file mode 100644 index 0000000000..e7967466c9 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/request/SessionUnregistrationRequest.java @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fitness.request; + +import android.app.PendingIntent; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.api.internal.IStatusCallback; +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 SessionUnregistrationRequest extends AbstractSafeParcelable { + @Field(1) + public PendingIntent intent; + @Field(2) + public IStatusCallback callback; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionUnregistrationRequest.class); +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataSourceStatsResult.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataSourceStatsResult.java index 82dd795d7d..d6d79a51b5 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataSourceStatsResult.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataSourceStatsResult.java @@ -13,7 +13,9 @@ import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; import com.google.android.gms.fitness.data.DataSource; +import org.microg.gms.common.Hide; +@Hide @SafeParcelable.Class public class DataSourceStatsResult extends AbstractSafeParcelable { @Field(1) diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataStatsResult.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataStatsResult.java index 54b9edb493..68a2c1d259 100644 --- a/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataStatsResult.java +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataStatsResult.java @@ -14,10 +14,12 @@ import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import org.microg.gms.common.Hide; import java.io.Closeable; import java.util.List; +@Hide @SafeParcelable.Class public class DataStatsResult extends AbstractSafeParcelable implements Closeable { @Field(1) diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataTypeResult.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataTypeResult.java new file mode 100644 index 0000000000..b8956449d3 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/DataTypeResult.java @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fitness.result; + +import android.app.Activity; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import androidx.annotation.Nullable; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.data.DataType; +import org.microg.gms.common.Hide; + +/** + * Result of {@link ConfigApi#readDataType(GoogleApiClient, String)}. + *

+ * The method {@link #getStatus()} can be used to confirm if the request was successful. On success, the returned data type can be accessed + * via {@link #getDataType()}. + *

+ * In case the calling app is missing the required permissions, the returned status has status code set to + * {@link FitnessStatusCodes#NEEDS_OAUTH_PERMISSIONS}. In this case the caller should use {@link Status#startResolutionForResult(Activity, int)} + * to start an intent to get the necessary consent from the user before retrying the request. + *

+ * In case the app attempts to read a custom data type created by other app, the returned status has status code set to + * {@link FitnessStatusCodes#INCONSISTENT_DATA_TYPE}. + * + * @deprecated No replacement. + */ +@Deprecated +@SafeParcelable.Class +public class DataTypeResult extends AbstractSafeParcelable { + + @Field(value = 1, getterName = "getStatus") + private final Status status; + @Field(value = 3, getterName = "getDataType") + @Nullable + private final DataType dataType; + + @Constructor + @Hide + public DataTypeResult(@Param(1) Status status, @Param(3) @Nullable DataType dataType) { + this.status = status; + this.dataType = dataType; + } + + /** + * Returns the new custom data type inserted, or {@code null} if the request failed. + */ + @Nullable + public DataType getDataType() { + return dataType; + } + + public Status getStatus() { + return status; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(DataTypeResult.class); + +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/SessionReadResult.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/SessionReadResult.java new file mode 100644 index 0000000000..c81d3a2431 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/SessionReadResult.java @@ -0,0 +1,133 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fitness.result; + +import android.app.Activity; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.data.DataSet; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.data.Session; +import com.google.android.gms.fitness.data.SessionDataSet; +import com.google.android.gms.fitness.request.SessionReadRequest; +import org.microg.gms.common.Hide; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Result of {@link SessionsApi#readSession(GoogleApiClient, SessionReadRequest)}. + * Contains all Sessions and their corresponding data sets that matched the filters specified in the {@link SessionReadRequest}. + *

+ * The method {@link #getStatus()} can be used to confirm if the request was successful. + *

+ * In case the calling app is missing the required permissions, the returned status has status code set to + * {@link FitnessStatusCodes#NEEDS_OAUTH_PERMISSIONS}. In this case the caller should use {@link Status#startResolutionForResult(Activity, int)} + * to start an intent to get the necessary consent from the user before retrying the request. + *

+ * The method {@link #getSessions()} returns all sessions that are returned for the request. The method {@link #getDataSet(Session, DataType)} returns + * {@link DataSet} for a particular Session and {@link DataType} from the result. + *

+ * In case the app tried to read data for a custom data type created by another app, the returned status has status code set to + * {@link FitnessStatusCodes#INCONSISTENT_DATA_TYPE}. + */ +@SafeParcelable.Class +public class SessionReadResult extends AbstractSafeParcelable { + @Field(value = 1, getterName = "getSessions") + @NonNull + private final List sessions; + @Field(value = 2, getterName = "getSessionDataSets") + @NonNull + private final List sessionDataSets; + @Field(value = 3, getterName = "getStatus") + @NonNull + private final Status status; + + @Constructor + @Hide + public SessionReadResult(@Param(1) @NonNull List sessions, @Param(2) @NonNull List sessionDataSets, @Param(3) @NonNull Status status) { + this.sessions = sessions; + this.sessionDataSets = sessionDataSets; + this.status = status; + } + + /** + * Returns the data sets for a given {@code session} and {@code dataType}. If a specific data source was requested for this data type in the read request, the + * returned data set is from that source. Else, the default data source for this data type is used. Returns empty if no data for the requested data + * type is found. + * + * @return Data sets for the given session and data type, empty if no data was found. Multiple data sets may be returned for a given type, based + * on the read request + * @throws IllegalArgumentException If the given session was not part of getSessions() output. + */ + @NonNull + public List getDataSet(@NonNull Session session, @NonNull DataType dataType) { + if (!sessions.contains(session)) throw new IllegalArgumentException("Attempting to read data for session which was not returned"); + List dataSets = new ArrayList<>(); + for (SessionDataSet sessionDataSet : this.sessionDataSets) { + if (session.equals(sessionDataSet.session) && dataType.equals(sessionDataSet.dataSet.getDataType())) { + dataSets.add(sessionDataSet.dataSet); + } + } + return dataSets; + } + + /** + * Returns the data sets for all data sources for a given {@code session}. If a specific data source was requested for a data type in the read request, + * the returned data set is from that source. Else, the default data source for the requested data type is used. + * + * @return Data sets for the given session for all data sources, empty if no data was found. Multiple data sets may be returned for a given type, + * based on the read request + * @throws IllegalArgumentException If the given session was not part of getSessions() output + */ + @NonNull + public List getDataSet(@NonNull Session session) { + if (!sessions.contains(session)) throw new IllegalArgumentException("Attempting to read data for session which was not returned"); + List dataSets = new ArrayList<>(); + for (SessionDataSet sessionDataSet : sessionDataSets) { + if (session.equals(sessionDataSet.session)) { + dataSets.add(sessionDataSet.dataSet); + } + } + return dataSets; + } + + /** + * Returns all sessions that matched the requested filters. + */ + @NonNull + public List getSessions() { + return this.sessions; + } + + @NonNull + public Status getStatus() { + return this.status; + } + + @NonNull + List getSessionDataSets() { + return sessionDataSets; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionReadResult.class); +} diff --git a/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/SessionStopResult.java b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/SessionStopResult.java new file mode 100644 index 0000000000..b4b4e6e5a1 --- /dev/null +++ b/play-services-fitness/src/main/java/com/google/android/gms/fitness/result/SessionStopResult.java @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fitness.result; + +import android.app.Activity; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; +import com.google.android.gms.fitness.data.Session; +import org.microg.gms.common.Hide; + +import java.util.List; + +/** + * Result of {@link SessionsApi#stopSession(GoogleApiClient, String)}. + *

+ * The method {@link #getStatus()} can be used to confirm if the request was successful. + *

+ * In case the calling app is missing the required permissions, the returned status has status code set to + * {@link FitnessStatusCodes#NEEDS_OAUTH_PERMISSIONS}. In this case the caller should use {@link Status#startResolutionForResult(Activity, int)} + * to start an intent to get the necessary consent from the user before retrying the request. + */ +@SafeParcelable.Class +public class SessionStopResult extends AbstractSafeParcelable { + @Field(value = 2, getterName = "getStatus") + @NonNull + private final Status status; + @Field(value = 3, getterName = "getSessions") + @NonNull + private final List sessions; + + @Constructor + @Hide + public SessionStopResult(@Param(2) @NonNull Status status, @Param(3) @NonNull List sessions) { + this.status = status; + this.sessions = sessions; + } + + /** + * Returns the list of sessions that were stopped by the request. Returns an empty list if no active session was stopped. + */ + @NonNull + public List getSessions() { + return sessions; + } + + /** + * Returns the status of the call to Google Fit. {@link Status#isSuccess()} can be used to determine whether the call succeeded. In the case of + * failure, you can inspect the status to determine the reason. + */ + @NonNull + public Status getStatus() { + return status; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(SessionStopResult.class); +} diff --git a/settings.gradle b/settings.gradle index 0c2ee077ef..eb719860b4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -89,6 +89,7 @@ include ':play-services-conscrypt-provider-core' sublude ':play-services-cronet:core' sublude ':play-services-droidguard:core' sublude ':play-services-fido:core' +sublude ':play-services-fitness:core' sublude ':play-services-gmscompliance:core' sublude ':play-services-location:core' sublude ':play-services-location:core:base'