Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e5668ac
Added bare layout for wearable TOS
deadYokai Dec 25, 2025
5ae5054
Added founded callbacks to IWearableCallbacks.aidl
deadYokai Dec 27, 2025
30a2cad
Added missing classes (stubs) + aidls
deadYokai Dec 27, 2025
75029a8
Added some aidl files and consent stuff
deadYokai Dec 28, 2025
54f8fc7
capability, connectionConfiguration rework
deadYokai Dec 29, 2025
f9c59c3
added services `getNodeId`, `sendRequest` and option variants of `sen…
deadYokai Dec 30, 2025
465f5f0
finaly successful connection
deadYokai Dec 31, 2025
af855ec
Some channel manager things
deadYokai Jan 4, 2026
63a77a0
Wearable lib relocation
deadYokai Jan 5, 2026
4e22cf9
updated NodeDatabaseHelper
deadYokai Jan 9, 2026
ab5c920
fix lint
deadYokai Jan 9, 2026
f9244e2
updated scary Bluetooth things
deadYokai Jan 12, 2026
a261776
Node & Assets db update
deadYokai Jan 13, 2026
8544d12
reworked Bluetooth
deadYokai Jan 14, 2026
1eacce1
update code
deadYokai Feb 6, 2026
5012986
implement few unimplemented methods
deadYokai Feb 7, 2026
6813117
Make use of packageName in ConnectionConfiguration
teccheck Feb 28, 2026
f8c8d69
Add WearOS config UI
teccheck Feb 28, 2026
b828883
Fix channel messages
teccheck Mar 1, 2026
a724559
code update
deadYokai Mar 1, 2026
80792b4
node migration update
deadYokai Mar 1, 2026
2d54f91
feat(wear): add runtimeType to ConnectionConfiguration (field 20)
teccheck Mar 4, 2026
3b2adf8
fix(wear): fix Connect message proto
teccheck Mar 4, 2026
7dd589e
feat(wear): add missing proto definitions
teccheck Mar 4, 2026
6d3fc68
feat(wear): add Wearable Reader and Writer
deadYokai Mar 4, 2026
048aa39
feat(wear): add missing messages to MessageHandler and MessageListener
deadYokai Mar 4, 2026
9d96a77
feat(wear): add NetworkConnectionManager
deadYokai Mar 4, 2026
05f76ca
rough ble implementation
deadYokai Mar 4, 2026
c51ede6
fix Type enum in wearable.proto
deadYokai Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions play-services-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ dependencies {
implementation project(':play-services-safetynet')
implementation project(':play-services-tasks-ktx')
implementation project(':play-services-fitness')
implementation project(':play-services-wearable')

mapboxRuntimeOnly project(':play-services-maps-core-mapbox')
vtmRuntimeOnly project(':play-services-maps-core-vtm')
Expand Down
1 change: 1 addition & 0 deletions play-services-core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@
<activity
android:name="com.google.android.gms.wearable.consent.TermsOfServiceActivity"
android:process=":ui"
android:exported="true"
android:configChanges="screenSize|orientation|keyboardHidden">
<intent-filter>
<action android:name="com.google.android.gms.wearable.TOS"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,51 @@

package com.google.android.gms.wearable.consent

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.R
import com.google.android.material.button.MaterialButton


class TermsOfServiceActivity : AppCompatActivity() {

private var acceptButton: MaterialButton? = null
private var declineButton: MaterialButton? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED)
// setResult(RESULT_CANCELED)
// finish()

// TODO: make consent list
setContentView(R.layout.activity_wearable_tos);

acceptButton = findViewById(R.id.terms_of_service_accept_button);
declineButton = findViewById(R.id.terms_of_service_decline_button);

acceptButton?.setOnClickListener { acceptConsents() }
declineButton?.setOnClickListener { declineConsents() }

}

private fun acceptConsents() {
val result = Intent().apply {
putExtra("consents_accepted", true)
putExtra("tos_accepted", true)
putExtra("privacy_policy_accepted", true)
}

setResult(RESULT_OK, result)
finish()
}

private fun declineConsents() {
val result = Intent().apply {
putExtra("consents_accepted", false)
}

setResult(RESULT_CANCELED, result)
finish()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class SettingsFragment : ResourceSettingsFragment() {
findNavController().navigate(requireContext(), R.id.openWorkProfileSettings)
true
}
findPreference<Preference>(PREF_WEAR)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
findNavController().navigate(requireContext(), R.id.openWearSettings)
true
}

findPreference<Preference>(PREF_ABOUT)!!.apply {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
Expand Down Expand Up @@ -138,6 +142,7 @@ class SettingsFragment : ResourceSettingsFragment() {
const val PREF_CHECKIN = "pref_checkin"
const val PREF_VENDING = "pref_vending"
const val PREF_WORK_PROFILE = "pref_work_profile"
const val PREF_WEAR = "pref_wear"
const val PREF_ACCOUNTS = "pref_accounts"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* SPDX-FileCopyrightText: 2026, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.ui

import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.lifecycleScope
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.google.android.gms.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.microg.gms.wearable.ConfigurationDatabaseHelper

class WearConfigFragment : PreferenceFragmentCompat() {

private lateinit var appHeadingPreference: AppHeadingPreference
private lateinit var configNamePreference: EditTextPreference
private lateinit var configEnabledPreference: SwitchPreferenceCompat
private lateinit var configAddressPreference: EditTextPreference
private lateinit var configPackageNamePreference: EditTextPreference
private lateinit var configDeletePreference: Preference

private lateinit var database: ConfigurationDatabaseHelper
private val configName: String?
get() = arguments?.getString("name")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
database = ConfigurationDatabaseHelper(context)
}

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_wear_config)
}

@SuppressLint("RestrictedApi")
override fun onBindPreferences() {
appHeadingPreference =
preferenceScreen.findPreference("pref_wear_config_heading") ?: appHeadingPreference
configNamePreference =
preferenceScreen.findPreference("pref_wear_config_name") ?: configNamePreference
configEnabledPreference =
preferenceScreen.findPreference("pref_wear_config_enabled") ?: configEnabledPreference
configAddressPreference =
preferenceScreen.findPreference("pref_wear_config_address") ?: configAddressPreference
configPackageNamePreference =
preferenceScreen.findPreference("pref_wear_config_package_name")
?: configPackageNamePreference
configDeletePreference = preferenceScreen.findPreference("pref_wear_config_delete")
?: configDeletePreference

configEnabledPreference.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
val config = database.getConfiguration(configName)
config.enabled = newValue as Boolean
database.putConfiguration(config)
database.close()
true
}

configAddressPreference.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
val config = database.getConfiguration(configName)
config.address = newValue as String
database.putConfiguration(config)
database.close()
true
}

configPackageNamePreference.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
Log.d(TAG, "PackageName changed: $newValue")
val config = database.getConfiguration(configName)
config.packageName = newValue as String
database.putConfiguration(config)
database.close()
true
}

configDeletePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
showDeleteConfirm()
true
}
}

private fun showDeleteConfirm() {
requireContext().buildAlertDialog()
.setTitle(getString(R.string.wear_delete_confirm_title, configName))
.setPositiveButton(android.R.string.yes) { _, _ -> delete() }
.setNegativeButton(android.R.string.no) { _, _ -> }
.show()
}

private fun delete() {
lifecycleScope.launchWhenResumed {
withContext(Dispatchers.IO) {
database.deleteConfiguration(configName)
database.close()
// TODO: Leave fragment
}
}
}

override fun onResume() {
super.onResume()
updateContent()
}

override fun onPause() {
super.onPause()
database.close()
}

private fun updateContent() {
lifecycleScope.launchWhenResumed {
configNamePreference.text = configName
val config =
configName?.let { database.getConfiguration(it) } ?: return@launchWhenResumed
appHeadingPreference.packageName = config.packageName
configEnabledPreference.isChecked = config.enabled
configAddressPreference.text = config.address
configPackageNamePreference.text = config.packageName

database.close()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.microg.gms.ui

import android.content.Context
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.widget.ImageView
import androidx.appcompat.content.res.AppCompatResources
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.google.android.gms.wearable.ConnectionConfiguration

class WearConfigPreference: Preference {
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)

constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)

constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context) : super(context)

init {
isPersistent = false
}

private var configField: ConnectionConfiguration? = null

var connectionConfig: ConnectionConfiguration?
get() = configField
set(value) {
if (value == null && configField != null) {
title = null
icon = null
} else if (value != null) {
val pm = context.packageManager
val applicationInfo = pm.getApplicationInfoIfExists(value.packageName)

title = value.name
summary = value.packageName
icon = applicationInfo?.loadIcon(pm) ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon)
}
configField = value
}

override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val icon = holder.findViewById(android.R.id.icon)
if (icon is ImageView) {
icon.adjustViewBounds = true
icon.scaleType = ImageView.ScaleType.CENTER_INSIDE
icon.maxHeight = (32.0 * context.resources.displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT).toInt()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2026, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.ui

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import com.google.android.gms.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.microg.gms.wearable.ConfigurationDatabaseHelper

class WearFragment : PreferenceFragmentCompat() {

private lateinit var wearConnections: PreferenceCategory
private lateinit var wearConnectionsAll: Preference
private lateinit var wearConnectionsNone: Preference
private lateinit var database: ConfigurationDatabaseHelper

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
database = ConfigurationDatabaseHelper(context)
}

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_wear)
}

@SuppressLint("RestrictedApi")
override fun onBindPreferences() {
wearConnections =
preferenceScreen.findPreference("prefcat_wear_connections") ?: wearConnections
wearConnectionsAll =
preferenceScreen.findPreference("pref_wear_connections_all") ?: wearConnectionsAll
wearConnectionsNone =
preferenceScreen.findPreference("pref_wear_connections_none") ?: wearConnectionsNone
}

override fun onResume() {
super.onResume()
updateContent()
}

override fun onPause() {
super.onPause()
database.close()
}

private fun updateContent() {
lifecycleScope.launchWhenResumed {
val context = requireContext()

val (configs, showAll) = withContext(Dispatchers.IO) {
val configs = database.allConfigurations
val res = configs.take(3).mapIndexed { idx, config ->
val pref = WearConfigPreference(context)
pref.order = idx
pref.connectionConfig = config
pref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
findNavController().navigate(requireContext(), R.id.openWearConfigDetails, bundleOf(
"name" to config.name
))
true
}
pref.key = "pref_wear_conection_" + config.packageName
pref
}.let { it to (it.size < configs.size) }
database.close()
res
}

wearConnectionsAll.isVisible = showAll
wearConnections.removeAll()
for (config in configs) {
wearConnections.addPreference(config)
}
if (showAll) {
wearConnections.addPreference(wearConnectionsAll)
} else if (configs.isEmpty()) {
wearConnections.addPreference(wearConnectionsNone)
}
}
}
}
12 changes: 12 additions & 0 deletions play-services-core/src/main/res/drawable/ic_watch.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="@android:color/white"
android:pathData="M20,12c0,-2.54 -1.19,-4.81 -3.04,-6.27L16,0H8l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24h8l0.96,-5.73C18.81,16.81 20,14.54 20,12zM6,12c0,-3.31 2.69,-6 6,-6s6,2.69 6,6 -2.69,6 -6,6 -6,-2.69 -6,-6z" />

</vector>
Loading