Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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/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()
}
}
66 changes: 66 additions & 0 deletions play-services-core/src/main/res/layout/activity_wearable_tos.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<androidx.core.widget.NestedScrollView
android:id="@+id/terms_of_service_scroll_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/header_image"
android:layout_height="48dp"
android:layout_width="48dp"/>
<TextView
android:id="@+id/terms_of_service_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Terms of service"/>
</LinearLayout>
<TextView
android:id="@+id/terms_of_service_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/terms_of_service_list"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/terms_of_service_accept_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I agree"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/terms_of_service_decline_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Decline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.data.DataHolder;
Expand All @@ -27,15 +28,20 @@

import org.microg.gms.common.PackageUtils;

import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;

public class CapabilityManager {
private static final String TAG = "CapabilityManager";

private static final Uri ROOT = Uri.parse("wear:/capabilities/");
private final Context context;
private final WearableImpl wearable;
private final String packageName;

private final Object lock = new Object();

private Set<String> capabilities = new HashSet<String>();

public CapabilityManager(Context context, WearableImpl wearable, String packageName) {
Expand All @@ -44,6 +50,35 @@ public CapabilityManager(Context context, WearableImpl wearable, String packageN
this.packageName = packageName;
}

public enum CapabilityType {
STATIC("s", "+", "+#"),
DYNAMIC("d", "-", "-#");

public final String typeCode;
public final String addSymbol;
public final String addSymbolWithHash;

CapabilityType(String typeCode, String addSymbol, String addSymbolWithHash) {
this.typeCode = typeCode;
this.addSymbol = addSymbol;
this.addSymbolWithHash = addSymbolWithHash;
}

public static CapabilityType fromBytes(byte[] data) {
if (data == null || data.length == 0) return DYNAMIC;

String code = new String(data, 0, 1, StandardCharsets.UTF_8);

if (STATIC.typeCode.equals(code)) return STATIC;

return DYNAMIC;
}

public byte[] toBytes() {
return typeCode.getBytes(StandardCharsets.UTF_8);
}
}

private Uri buildCapabilityUri(String capability, boolean withAuthority) {
Uri.Builder builder = ROOT.buildUpon();
if (withAuthority) builder.authority(wearable.getLocalNodeId());
Expand All @@ -56,30 +91,110 @@ private Uri buildCapabilityUri(String capability, boolean withAuthority) {
public Set<String> getNodesForCapability(String capability) {
DataHolder dataHolder = wearable.getDataItemsByUriAsHolder(buildCapabilityUri(capability, false), packageName);
Set<String> nodes = new HashSet<>();
for (int i = 0; i < dataHolder.getCount(); i++) {
nodes.add(dataHolder.getString("host", i, 0));
try{
for (int i = 0; i < dataHolder.getCount(); i++) {
nodes.add(dataHolder.getString("host", i, 0));
}
} finally {
dataHolder.close();
}
dataHolder.close();
return nodes;
}

public int add(String capability) {
if (this.capabilities.contains(capability)) {
return WearableStatusCodes.DUPLICATE_CAPABILITY;
return addWithType(capability, CapabilityType.DYNAMIC);
// if (this.capabilities.contains(capability)) {
// return WearableStatusCodes.DUPLICATE_CAPABILITY;
// }
// DataItemInternal dataItem = new DataItemInternal(buildCapabilityUri(capability, true));
// DataItemRecord record = wearable.putDataItem(packageName, PackageUtils.firstSignatureDigest(context, packageName), wearable.getLocalNodeId(), dataItem);
// this.capabilities.add(capability);
// wearable.syncRecordToAll(record);
// return CommonStatusCodes.SUCCESS;
}

public int addWithType(String capability, CapabilityType type) {
synchronized (lock) {
Uri uri = buildCapabilityUri(capability, true);
DataHolder existingData = wearable.getDataItemsByUriAsHolder(uri, packageName);

try {
if (existingData.getCount() > 0) {
byte[] data = existingData.getByteArray("data", 0, 0);
CapabilityType existingType = CapabilityType.fromBytes(data);

if (existingType == CapabilityType.STATIC || type == CapabilityType.DYNAMIC) {
return WearableStatusCodes.DUPLICATE_CAPABILITY;
}
}
} finally {
existingData.close();
}

DataItemInternal dataItem = new DataItemInternal(uri);
dataItem.data = type.toBytes();

DataItemRecord record = wearable.putDataItem(
packageName,
PackageUtils.firstSignatureDigest(context, packageName),
wearable.getLocalNodeId(),
dataItem
);

if (record != null) {
capabilities.add(capability);
wearable.syncRecordToAll(record);
Log.d(TAG, "Added capability: " + capability + " (type=" + type + ")");
return CommonStatusCodes.SUCCESS;
} else {
Log.e(TAG, "Failed to add capability: " + capability);
return CommonStatusCodes.ERROR;
}
}
DataItemInternal dataItem = new DataItemInternal(buildCapabilityUri(capability, true));
DataItemRecord record = wearable.putDataItem(packageName, PackageUtils.firstSignatureDigest(context, packageName), wearable.getLocalNodeId(), dataItem);
this.capabilities.add(capability);
wearable.syncRecordToAll(record);
return CommonStatusCodes.SUCCESS;
}

public int remove(String capability) {
if (!this.capabilities.contains(capability)) {
return WearableStatusCodes.UNKNOWN_CAPABILITY;
synchronized (lock) {
if (!capabilities.contains(capability)) {
Uri uri = buildCapabilityUri(capability, true);
DataHolder existingData = wearable.getDataItemsByUriAsHolder(uri, packageName);
try {
if (existingData.getCount() == 0) {
Log.w(TAG, "Capability not found: " + capability);
return WearableStatusCodes.UNKNOWN_CAPABILITY;
}
} finally {
existingData.close();
}
}

// if (!this.capabilities.contains(capability)) {
// return WearableStatusCodes.UNKNOWN_CAPABILITY;
// }
wearable.deleteDataItems(buildCapabilityUri(capability, true), packageName);
capabilities.remove(capability);
Log.d(TAG, "Removed capability: " + capability);
return CommonStatusCodes.SUCCESS;
}
}

public Set<String> getLocalCapabilities() {
synchronized (lock) {
return new HashSet<>(capabilities);
}
}

public boolean hasCapability(String capability) {
synchronized (lock) {
return capabilities.contains(capability);
}
}

public void clearAll() {
synchronized (lock) {
for (String capability: new HashSet<>(capabilities)) {
remove(capability);
}
}
wearable.deleteDataItems(buildCapabilityUri(capability, true), packageName);
capabilities.remove(capability);
return CommonStatusCodes.SUCCESS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import android.os.RemoteException;

import com.google.android.gms.common.Feature;
import com.google.android.gms.common.internal.ConnectionInfo;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.IGmsCallbacks;

Expand All @@ -29,6 +31,40 @@ public class WearableService extends BaseService {

private WearableImpl wearable;

// All what i found
// for now, just to not spam outdated GMS at my companion
public static final Feature[] FEATURES = new Feature[]{
new Feature("app_client", 4L),
new Feature("carrier_auth", 1L),
new Feature("wear3_oem_companion", 1L),
new Feature("wear_await_data_sync_complete", 1L),
new Feature("wear_backup_restore", 6L),
new Feature("wear_consent", 2L),
new Feature("wear_consent_recordoptin", 1L),
new Feature("wear_consent_recordoptin_swaadl", 1L),
new Feature("wear_consent_supervised", 2L),
new Feature("wear_get_phone_switching_feature_status", 1L),
new Feature("wear_fast_pair_account_key_sync", 1L),
new Feature("wear_fast_pair_get_account_keys", 1L),
new Feature("wear_fast_pair_get_account_key_by_account", 1L),
new Feature("wear_flush_batched_data", 1L),
new Feature("wear_get_related_configs", 1L),
new Feature("wear_get_node_id", 1L),
new Feature("wear_logging_service", 2L),
new Feature("wear_retry_connection", 1L),
new Feature("wear_set_cloud_sync_setting_by_node", 1L),
new Feature("wear_first_party_consents", 2L),
new Feature("wear_update_config", 1L),
new Feature("wear_update_connection_retry_strategy", 1L),
new Feature("wear_update_delay_config", 1L),
new Feature("wearable_services", 1L),
new Feature("wear_cancel_migration", 1L),
new Feature("wear_customizable_screens", 2L),
new Feature("wear_wifi_immediate_connect", 1L),
new Feature("wear_get_node_active_network_metered", 1L),
new Feature("wear_consents_per_watch", 3L)
};

public WearableService() {
super("GmsWearSvc", GmsService.WEAR);
}
Expand All @@ -50,6 +86,9 @@ public void onDestroy() {
@Override
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
PackageUtils.getAndCheckCallingPackage(this, request.packageName);
callback.onPostInitComplete(0, new WearableServiceImpl(this, wearable, request.packageName), null);
ConnectionInfo connectionInfo = new ConnectionInfo();
connectionInfo.features = FEATURES;
callback.onPostInitCompleteWithConnectionInfo(0, new WearableServiceImpl(this, wearable, request.packageName), connectionInfo);

}
}
Loading