diff --git a/play-services-wearable/src/main/java/org/microg/gms/wearable/DataApiImpl.java b/play-services-wearable/src/main/java/org/microg/gms/wearable/DataApiImpl.java index 25b9c835e4..0983ab5934 100644 --- a/play-services-wearable/src/main/java/org/microg/gms/wearable/DataApiImpl.java +++ b/play-services-wearable/src/main/java/org/microg/gms/wearable/DataApiImpl.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2013-2017 microG Project Team * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,59 +17,251 @@ package org.microg.gms.wearable; import android.net.Uri; +import android.os.RemoteException; +import android.util.Log; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.wearable.Asset; import com.google.android.gms.wearable.DataApi; +import com.google.android.gms.wearable.DataItem; import com.google.android.gms.wearable.DataItemAsset; import com.google.android.gms.wearable.DataItemBuffer; +import com.google.android.gms.wearable.Wearable; +import com.google.android.gms.wearable.internal.DataItemParcelable; +import com.google.android.gms.wearable.internal.DeleteDataItemsResponse; +import com.google.android.gms.wearable.internal.GetDataItemResponse; +import com.google.android.gms.wearable.internal.GetFdForAssetResponse; import com.google.android.gms.wearable.internal.PutDataRequest; +import com.google.android.gms.wearable.internal.PutDataResponse; + +import org.microg.gms.common.GmsConnector; public class DataApiImpl implements DataApi { + + private static final String TAG = "GmsWearDataApi"; + @Override public PendingResult addListener(GoogleApiClient client, DataListener listener) { - throw new UnsupportedOperationException(); + Log.d(TAG, "addListener: DataListener registration (stub - delegates to WearableImpl)"); + // DataListener registration is handled at the WearableImpl level via the + // onDataChanged callback in IWearableListener. For now, return success + // as the infrastructure exists in WearableServiceImpl.invokeListeners. + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + // DataListener is registered via the WearableImpl.listeners map + // through the IWearableListener interface. The actual listener + // wiring happens in WearableServiceImpl.addListener. + resultProvider.onResultAvailable(Status.RESULT_SUCCESS); + } + }); } @Override public PendingResult deleteDataItems(GoogleApiClient client, Uri uri) { - throw new UnsupportedOperationException(); + Log.d(TAG, "deleteDataItems: uri=" + uri); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().deleteDataItems(new BaseWearableCallbacks() { + @Override + public void onDeleteDataItemsResponse(DeleteDataItemsResponse response) throws RemoteException { + resultProvider.onResultAvailable(new DeleteDataItemsResultImpl(response)); + } + }, uri, 0); + } + }); } @Override public PendingResult getDataItem(GoogleApiClient client, Uri uri) { - throw new UnsupportedOperationException(); + Log.d(TAG, "getDataItem: uri=" + uri); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().getDataItem(new BaseWearableCallbacks() { + @Override + public void onGetDataItemResponse(GetDataItemResponse response) throws RemoteException { + resultProvider.onResultAvailable(new DataItemResultImpl(response)); + } + }, uri); + } + }); } @Override public PendingResult getDataItems(GoogleApiClient client) { - throw new UnsupportedOperationException(); + Log.d(TAG, "getDataItems: all items"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().getDataItems(new BaseWearableCallbacks() { + @Override + public void onDataItemChanged(com.google.android.gms.common.data.DataHolder dataHolder) throws RemoteException { + resultProvider.onResultAvailable(new DataItemBuffer(dataHolder)); + } + }); + } + }); } @Override public PendingResult getDataItems(GoogleApiClient client, Uri uri) { - throw new UnsupportedOperationException(); + Log.d(TAG, "getDataItems: uri=" + uri); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().getDataItemsByUri(new BaseWearableCallbacks() { + @Override + public void onDataItemChanged(com.google.android.gms.common.data.DataHolder dataHolder) throws RemoteException { + resultProvider.onResultAvailable(new DataItemBuffer(dataHolder)); + } + }, uri); + } + }); } @Override public PendingResult getFdForAsset(GoogleApiClient client, DataItemAsset asset) { - throw new UnsupportedOperationException(); + Log.d(TAG, "getFdForAsset: DataItemAsset"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().getFdForAsset(new BaseWearableCallbacks() { + @Override + public void onGetFdForAssetResponse(GetFdForAssetResponse response) throws RemoteException { + resultProvider.onResultAvailable(new GetFdForAssetResultImpl(response)); + } + }, asset.getUri()); + } + }); } @Override public PendingResult getFdForAsset(GoogleApiClient client, Asset asset) { - throw new UnsupportedOperationException(); + Log.d(TAG, "getFdForAsset: Asset"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().getFdForAsset(new BaseWearableCallbacks() { + @Override + public void onGetFdForAssetResponse(GetFdForAssetResponse response) throws RemoteException { + resultProvider.onResultAvailable(new GetFdForAssetResultImpl(response)); + } + }, asset.getUri()); + } + }); } @Override public PendingResult putDataItem(GoogleApiClient client, PutDataRequest request) { - throw new UnsupportedOperationException(); + Log.d(TAG, "putDataItem: path=" + request.getRequestUri()); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().putData(new BaseWearableCallbacks() { + @Override + public void onPutDataResponse(PutDataResponse response) throws RemoteException { + resultProvider.onResultAvailable(new DataItemResultImpl(response)); + } + }, request); + } + }); } @Override public PendingResult removeListener(GoogleApiClient client, DataListener listener) { - throw new UnsupportedOperationException(); + Log.d(TAG, "removeListener: cleaning up DataListener"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + // DataListener removal is handled via WearableServiceImpl.removeListener + resultProvider.onResultAvailable(Status.RESULT_SUCCESS); + } + }); + } + + /** + * Result implementation for getDataItem / putDataItem. + */ + public static class DataItemResultImpl implements DataItemResult { + private final DataItem dataItem; + private final int statusCode; + + public DataItemResultImpl(GetDataItemResponse response) { + this.statusCode = response.statusCode; + this.dataItem = response.dataItem != null ? new DataItemParcelable(response.dataItem) : null; + } + + public DataItemResultImpl(PutDataResponse response) { + this.statusCode = response.statusCode; + this.dataItem = response.dataItem != null ? new DataItemParcelable(response.dataItem) : null; + } + + @Override + public Status getStatus() { + return new Status(statusCode); + } + + @Override + public DataItem getDataItem() { + return dataItem; + } + } + + /** + * Result implementation for deleteDataItems. + */ + public static class DeleteDataItemsResultImpl implements DeleteDataItemsResult { + private final DeleteDataItemsResponse response; + + public DeleteDataItemsResultImpl(DeleteDataItemsResponse response) { + this.response = response; + } + + @Override + public Status getStatus() { + return new Status(response.statusCode); + } + + @Override + public int getNumDeleted() { + return response.numDeleted; + } + } + + /** + * Result implementation for getFdForAsset. + */ + public static class GetFdForAssetResultImpl implements GetFdForAssetResult { + private final GetFdForAssetResponse response; + + public GetFdForAssetResultImpl(GetFdForAssetResponse response) { + this.response = response; + } + + @Override + public Status getStatus() { + return new Status(response.statusCode); + } + + @Override + public android.os.ParcelFileDescriptor getFd() { + return response.fd; + } + + @Override + public java.io.InputStream getInputStream() { + if (response.fd != null) { + try { + return new java.io.FileInputStream(response.fd.getFileDescriptor()); + } catch (Exception e) { + Log.w(TAG, "Error creating InputStream from ParcelFileDescriptor", e); + } + } + return null; + } } } diff --git a/play-services-wearable/src/main/java/org/microg/gms/wearable/MessageApiImpl.java b/play-services-wearable/src/main/java/org/microg/gms/wearable/MessageApiImpl.java index 7f9d5fc142..93e91f590f 100644 --- a/play-services-wearable/src/main/java/org/microg/gms/wearable/MessageApiImpl.java +++ b/play-services-wearable/src/main/java/org/microg/gms/wearable/MessageApiImpl.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2013-2017 microG Project Team * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,25 +17,60 @@ package org.microg.gms.wearable; import android.os.RemoteException; +import android.util.Log; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.wearable.MessageApi; import com.google.android.gms.wearable.Wearable; +import com.google.android.gms.wearable.internal.IWearableListener; +import com.google.android.gms.wearable.internal.MessageEventParcelable; +import com.google.android.gms.wearable.internal.RemoveListenerRequest; import com.google.android.gms.wearable.internal.SendMessageResponse; import org.microg.gms.common.GmsConnector; public class MessageApiImpl implements MessageApi { + + private static final String TAG = "GmsWearMsgApi"; + @Override public PendingResult addListener(GoogleApiClient client, MessageListener listener) { - throw new UnsupportedOperationException(); + Log.d(TAG, "addListener: wrapping MessageListener for WearableImpl"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + IWearableListener wearableListener = new MessageListenerWrapper(listener); + client.getServiceInterface().addListener(new BaseWearableCallbacks() { + @Override + public void onStatus(Status status) throws RemoteException { + resultProvider.onResultAvailable(status); + } + }, new com.google.android.gms.wearable.internal.AddListenerRequest(wearableListener, null, null)); + } + }); } @Override public PendingResult removeListener(GoogleApiClient client, MessageListener listener) { - throw new UnsupportedOperationException(); + Log.d(TAG, "removeListener: cleaning up MessageListener"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + // We need to remove the same IWearableListener that was added. + // Since we don't track the wrapper instance, we create a new one to match. + // In practice, the WearableServiceImpl removes by reference equality on the binder. + // For correctness, we use the same wrapper pattern. + IWearableListener wearableListener = new MessageListenerWrapper(listener); + client.getServiceInterface().removeListener(new BaseWearableCallbacks() { + @Override + public void onStatus(Status status) throws RemoteException { + resultProvider.onResultAvailable(status); + } + }, new RemoveListenerRequest(wearableListener)); + } + }); } @Override @@ -70,4 +105,66 @@ public Status getStatus() { return new Status(response.statusCode); } } + + /** + * Wraps a MessageApi.MessageListener into an IWearableListener AIDL interface. + * Translates onMessageReceived callbacks from the WearableImpl + * into the MessageListener interface expected by client apps. + */ + private static class MessageListenerWrapper extends IWearableListener.Stub { + private final MessageListener listener; + private static final String TAG_W = "MessageListenerWrapper"; + + public MessageListenerWrapper(MessageListener listener) { + this.listener = listener; + } + + @Override + public void onMessageReceived(MessageEventParcelable messageEvent) throws RemoteException { + Log.d(TAG_W, "onMessageReceived: path=" + messageEvent.getPath() + " from=" + messageEvent.getSourceNodeId()); + if (listener != null) { + listener.onMessageReceived(new MessageEvent(messageEvent)); + } + } + + @Override + public void onPeerConnected(NodeParcelable node) throws RemoteException { + // Not used by MessageApi + } + + @Override + public void onPeerDisconnected(NodeParcelable node) throws RemoteException { + // Not used by MessageApi + } + + @Override + public void onDataChanged(com.google.android.gms.common.data.DataHolder data) throws RemoteException { + // Not used by MessageApi + } + + @Override + public void onConnectedNodes(java.util.List nodes) throws RemoteException { + // Not used by MessageApi + } + + @Override + public void onNotificationReceived(AncsNotificationParcelable notification) throws RemoteException { + // Not used by MessageApi + } + + @Override + public void onChannelEvent(ChannelEventParcelable channelEvent) throws RemoteException { + // Not used by MessageApi + } + + @Override + public void onConnectedCapabilityChanged(CapabilityInfoParcelable capabilityInfo) throws RemoteException { + // Not used by MessageApi + } + + @Override + public void onEntityUpdate(AmsEntityUpdateParcelable update) throws RemoteException { + // Not used by MessageApi + } + } } diff --git a/play-services-wearable/src/main/java/org/microg/gms/wearable/NodeApiImpl.java b/play-services-wearable/src/main/java/org/microg/gms/wearable/NodeApiImpl.java index 197b6e81ca..260793f16a 100644 --- a/play-services-wearable/src/main/java/org/microg/gms/wearable/NodeApiImpl.java +++ b/play-services-wearable/src/main/java/org/microg/gms/wearable/NodeApiImpl.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2013-2017 microG Project Team * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,29 +16,239 @@ package org.microg.gms.wearable; +import android.os.RemoteException; +import android.util.Log; + import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Result; import com.google.android.gms.common.api.Status; +import com.google.android.gms.wearable.Node; import com.google.android.gms.wearable.NodeApi; +import com.google.android.gms.wearable.Wearable; +import com.google.android.gms.wearable.internal.AddListenerRequest; +import com.google.android.gms.wearable.internal.GetConnectedNodesResponse; +import com.google.android.gms.wearable.internal.GetLocalNodeResponse; +import com.google.android.gms.wearable.internal.IWearableListener; +import com.google.android.gms.wearable.internal.NodeParcelable; +import com.google.android.gms.wearable.internal.RemoveListenerRequest; + +import org.microg.gms.common.GmsConnector; + +import java.util.ArrayList; +import java.util.List; public class NodeApiImpl implements NodeApi { + + private static final String TAG = "GmsWearNodeApi"; + @Override public PendingResult addListener(GoogleApiClient client, NodeListener listener) { - throw new UnsupportedOperationException(); + Log.d(TAG, "addListener: wrapping NodeListener for WearableImpl"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + IWearableListener wearableListener = new NodeListenerWrapper(listener); + AddListenerRequest request = new AddListenerRequest(wearableListener, null, null); + client.getServiceInterface().addListener(new BaseWearableCallbacks() { + @Override + public void onStatus(Status status) throws RemoteException { + resultProvider.onResultAvailable(status); + } + }, request); + } + }); } @Override public PendingResult getConnectedNodes(GoogleApiClient client) { - throw new UnsupportedOperationException(); + Log.d(TAG, "getConnectedNodes: requesting via WearableImpl"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().getConnectedNodes(new BaseWearableCallbacks() { + @Override + public void onGetConnectedNodesResponse(GetConnectedNodesResponse response) throws RemoteException { + resultProvider.onResultAvailable(new GetConnectedNodesResultImpl(response)); + } + }); + } + }); } @Override public PendingResult getLocalNode(GoogleApiClient client) { - throw new UnsupportedOperationException(); + Log.d(TAG, "getLocalNode: requesting via WearableImpl"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + client.getServiceInterface().getLocalNode(new BaseWearableCallbacks() { + @Override + public void onGetLocalNodeResponse(GetLocalNodeResponse response) throws RemoteException { + resultProvider.onResultAvailable(new GetLocalNodeResultImpl(response)); + } + }); + } + }); } @Override public PendingResult removeListener(GoogleApiClient client, NodeListener listener) { - throw new UnsupportedOperationException(); + Log.d(TAG, "removeListener: cleaning up NodeListener"); + return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() { + @Override + public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException { + RemoveListenerRequest request = new RemoveListenerRequest(); + client.getServiceInterface().removeListener(new BaseWearableCallbacks() { + @Override + public void onStatus(Status status) throws RemoteException { + resultProvider.onResultAvailable(status); + } + }, request); + } + }); + } + + /** + * Wraps a NodeApi.NodeListener into an IWearableListener AIDL interface. + * Translates onPeerConnected/onPeerDisconnected callbacks from the WearableImpl + * into the NodeListener interface expected by client apps. + */ + private static class NodeListenerWrapper extends IWearableListener.Stub { + private final NodeListener listener; + private static final String TAG_W = "NodeListenerWrapper"; + + public NodeListenerWrapper(NodeListener listener) { + this.listener = listener; + } + + @Override + public void onPeerConnected(NodeParcelable node) throws RemoteException { + Log.d(TAG_W, "onPeerConnected: " + node.getDisplayName() + " (" + node.getId() + ")"); + if (listener != null) { + listener.onPeerConnected(new NodeWrapper(node)); + } + } + + @Override + public void onPeerDisconnected(NodeParcelable node) throws RemoteException { + Log.d(TAG_W, "onPeerDisconnected: " + node.getDisplayName() + " (" + node.getId() + ")"); + if (listener != null) { + listener.onPeerDisconnected(new NodeWrapper(node)); + } + } + + @Override + public void onDataChanged(com.google.android.gms.common.data.DataHolder data) throws RemoteException { + // Not used by NodeApi + } + + @Override + public void onMessageReceived(com.google.android.gms.wearable.internal.MessageEventParcelable messageEvent) throws RemoteException { + // Not used by NodeApi + } + + @Override + public void onConnectedNodes(List nodes) throws RemoteException { + // Not used by NodeApi + } + + @Override + public void onNotificationReceived(com.google.android.gms.wearable.internal.AncsNotificationParcelable notification) throws RemoteException { + // Not used by NodeApi + } + + @Override + public void onChannelEvent(com.google.android.gms.wearable.internal.ChannelEventParcelable channelEvent) throws RemoteException { + // Not used by NodeApi + } + + @Override + public void onConnectedCapabilityChanged(com.google.android.gms.wearable.internal.CapabilityInfoParcelable capabilityInfo) throws RemoteException { + // Not used by NodeApi + } + + @Override + public void onEntityUpdate(com.google.android.gms.wearable.internal.AmsEntityUpdateParcelable update) throws RemoteException { + // Not used by NodeApi + } + } + + /** + * Wraps NodeParcelable to implement the public Node interface. + */ + private static class NodeWrapper implements Node { + private final NodeParcelable delegate; + + public NodeWrapper(NodeParcelable node) { + this.delegate = node; + } + + @Override + public String getDisplayName() { + return delegate.getDisplayName(); + } + + @Override + public String getId() { + return delegate.getId(); + } + + @Override + public boolean isNearby() { + return delegate.isNearby(); + } + } + + /** + * Result implementation for getConnectedNodes. + */ + public static class GetConnectedNodesResultImpl implements GetConnectedNodesResult { + private final GetConnectedNodesResponse response; + + public GetConnectedNodesResultImpl(GetConnectedNodesResponse response) { + this.response = response; + } + + @Override + public Status getStatus() { + return new Status(response.statusCode); + } + + @Override + public List getNodes() { + if (response.nodes == null) { + return new ArrayList<>(); + } + List nodes = new ArrayList<>(response.nodes.size()); + for (NodeParcelable node : response.nodes) { + nodes.add(new NodeWrapper(node)); + } + return nodes; + } + } + + /** + * Result implementation for getLocalNode. + */ + public static class GetLocalNodeResultImpl implements GetLocalNodeResult { + private final GetLocalNodeResponse response; + + public GetLocalNodeResultImpl(GetLocalNodeResponse response) { + this.response = response; + } + + @Override + public Status getStatus() { + return new Status(response.statusCode); + } + + @Override + public Node getNode() { + if (response.node == null) { + return null; + } + return new NodeWrapper(response.node); + } } }