From 27c1c540d6b1d2a1d95494d1062580728bab4ae6 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 31 Jul 2016 13:01:27 +0200 Subject: [PATCH] Wearable: correctly implement listeners including remote binding --- .../gms/common/RemoteListenerProxy.java | 130 ++++++++++++++++++ .../microg/gms/wearable/DataItemRecord.java | 33 ++++- .../microg/gms/wearable/MessageHandler.java | 2 +- .../gms/wearable/NodeDatabaseHelper.java | 4 +- .../org/microg/gms/wearable/WearableImpl.java | 41 ++++-- .../gms/wearable/WearableServiceImpl.java | 11 +- 6 files changed, 201 insertions(+), 20 deletions(-) create mode 100644 play-services-core/src/main/java/org/microg/gms/common/RemoteListenerProxy.java diff --git a/play-services-core/src/main/java/org/microg/gms/common/RemoteListenerProxy.java b/play-services-core/src/main/java/org/microg/gms/common/RemoteListenerProxy.java new file mode 100644 index 00000000..36bd99f4 --- /dev/null +++ b/play-services-core/src/main/java/org/microg/gms/common/RemoteListenerProxy.java @@ -0,0 +1,130 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.gms.common; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ResolveInfo; +import android.os.IBinder; +import android.os.IInterface; +import android.util.Log; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +public class RemoteListenerProxy implements ServiceConnection, InvocationHandler { + private static final String TAG = "GmsRemoteListener"; + private final Context context; + private final Intent searchIntent; + private final String bindAction; + private IBinder remote; + private boolean connecting; + private List waiting = new ArrayList(); + private Class tClass; + + public static T get(Context context, Intent intent, Class tClass, String bindAction) { + return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, + new RemoteListenerProxy(context, intent, tClass, bindAction)); + } + + private RemoteListenerProxy(Context context, Intent intent, Class tClass, String bindAction) { + this.context = context; + this.searchIntent = intent; + this.tClass = tClass; + this.bindAction = bindAction; + } + + private boolean connect() { + synchronized (this) { + if (!connecting) { + try { + ResolveInfo resolveInfo = context.getPackageManager().resolveService(searchIntent, 0); + if (resolveInfo != null) { + Intent intent = new Intent(bindAction); + intent.setPackage(resolveInfo.serviceInfo.packageName); + intent.setClassName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); + connecting = context.bindService(intent, this, Context.BIND_AUTO_CREATE); + if (!connecting) Log.d(TAG, "Could not connect to: " + intent); + return connecting; + } + return false; + } catch (Exception e) { + Log.w(TAG, e); + } + } + return true; + } + } + + private void runOncePossible(Runnable runnable) { + synchronized (this) { + if (remote == null) { + waiting.add(runnable); + } else { + runnable.run(); + } + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (this) { + remote = service; + if (!waiting.isEmpty()) { + for (Runnable runnable : waiting) { + runnable.run(); + } + waiting.clear(); + context.unbindService(RemoteListenerProxy.this); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (this) { + remote = null; + } + } + + @Override + public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { + if (method.getDeclaringClass().equals(tClass)) { + runOncePossible(new Runnable() { + @Override + public void run() { + try { + Object asInterface = Class.forName(tClass.getName() + "$Stub").getMethod("asInterface", IBinder.class).invoke(null, remote); + method.invoke(asInterface, args); + } catch (Exception e) { + Log.w(TAG, e); + } + } + }); + connect(); + return null; + } else if (method.getDeclaringClass().equals(Object.class)) { + return method.invoke(this, args); + } + return null; + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/wearable/DataItemRecord.java b/play-services-core/src/main/java/org/microg/gms/wearable/DataItemRecord.java index 479a9b5e..9c8d85ce 100644 --- a/play-services-core/src/main/java/org/microg/gms/wearable/DataItemRecord.java +++ b/play-services-core/src/main/java/org/microg/gms/wearable/DataItemRecord.java @@ -20,6 +20,7 @@ import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; +import com.google.android.gms.common.data.DataHolder; import com.google.android.gms.wearable.Asset; import com.google.android.gms.wearable.internal.DataItemAssetParcelable; import com.google.android.gms.wearable.internal.DataItemParcelable; @@ -28,12 +29,15 @@ import org.microg.wearable.proto.AssetEntry; import org.microg.wearable.proto.SetDataItem; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import okio.ByteString; public class DataItemRecord { + private static String[] EVENT_DATA_HOLDER_FIELDS = new String[] { "event_type", "path", "data", "tags", "asset_key", "asset_id" }; + public DataItemInternal dataItem; public String source; public long seqId; @@ -44,7 +48,7 @@ public class DataItemRecord { public String packageName; public String signatureDigest; - public ContentValues getContentValues() { + public ContentValues toContentValues() { ContentValues contentValues = new ContentValues(); contentValues.put("sourceNode", source); contentValues.put("seqId", seqId); @@ -62,6 +66,33 @@ public class DataItemRecord { return contentValues; } + public DataHolder toEventDataHolder() { + DataHolder.Builder builder = DataHolder.builder(EVENT_DATA_HOLDER_FIELDS); + HashMap data = new HashMap(); + data.put("path", dataItem.uri.toString()); + if (deleted) { + data.put("event_type", 2); + builder.withRow(data); + } else { + data.put("event_type", 1); + data.put("data", dataItem.data); + data.put("tags", ""); + boolean added = false; + for (Map.Entry entry : dataItem.getAssets().entrySet()) { + added = true; + data.put("asset_id", entry.getValue().getDigest()); + data.put("asset_key", entry.getKey()); + builder.withRow(data); + data = new HashMap(); + data.put("path", dataItem.uri.toString()); + } + if (!added) { + builder.withRow(data); + } + } + return builder.build(0); + } + public DataItemParcelable toParcelable() { DataItemParcelable parcelable = new DataItemParcelable(dataItem.uri); parcelable.data = dataItem.data; diff --git a/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java b/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java index 07f66889..7da789ff 100644 --- a/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java +++ b/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java @@ -145,7 +145,7 @@ public class MessageHandler extends ServerMessageListener { messageEvent.requestId = rpcRequest.requestId + 31 * (rpcRequest.generation + 527); messageEvent.sourceNodeId = TextUtils.isEmpty(rpcRequest.sourceNodeId) ? peerNodeId : rpcRequest.sourceNodeId; - wearable.sendMessageReceived(messageEvent); + wearable.sendMessageReceived(rpcRequest.packageName, messageEvent); } else if (rpcRequest.targetNodeId.equals(peerNodeId)) { // Drop it, loop detection (yes we really need this in this protocol o.O) } else { diff --git a/play-services-core/src/main/java/org/microg/gms/wearable/NodeDatabaseHelper.java b/play-services-core/src/main/java/org/microg/gms/wearable/NodeDatabaseHelper.java index a40233d6..9ea6f3e9 100644 --- a/play-services-core/src/main/java/org/microg/gms/wearable/NodeDatabaseHelper.java +++ b/play-services-core/src/main/java/org/microg/gms/wearable/NodeDatabaseHelper.java @@ -147,13 +147,13 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper { } private static void updateRecord(SQLiteDatabase db, String key, DataItemRecord record) { - ContentValues cv = record.getContentValues(); + ContentValues cv = record.toContentValues(); db.update("dataitems", cv, "_id=?", new String[]{key}); finishRecord(db, key, record); } private static String insertRecord(SQLiteDatabase db, DataItemRecord record) { - ContentValues contentValues = record.getContentValues(); + ContentValues contentValues = record.toContentValues(); contentValues.put("appkeys_id", getAppKey(db, record.packageName, record.signatureDigest)); contentValues.put("host", record.dataItem.host); contentValues.put("path", record.dataItem.path); diff --git a/play-services-core/src/main/java/org/microg/gms/wearable/WearableImpl.java b/play-services-core/src/main/java/org/microg/gms/wearable/WearableImpl.java index 4255e8e7..249da20d 100644 --- a/play-services-core/src/main/java/org/microg/gms/wearable/WearableImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/wearable/WearableImpl.java @@ -17,10 +17,10 @@ package org.microg.gms.wearable; import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.RemoteException; -import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Base64; import android.util.Log; @@ -36,6 +36,7 @@ import com.google.android.gms.wearable.internal.PutDataRequest; import org.microg.gms.common.MultiListenerProxy; import org.microg.gms.common.PackageUtils; +import org.microg.gms.common.RemoteListenerProxy; import org.microg.gms.common.Utils; import org.microg.wearable.SocketConnectionThread; import org.microg.wearable.WearableConnection; @@ -55,11 +56,9 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -110,11 +109,8 @@ public class WearableImpl { public DataItemRecord putDataItem(DataItemRecord record) { nodeDatabase.putRecord(record); try { - if (listeners.containsKey(record.packageName)) { - MultiListenerProxy.get(IWearableListener.class, listeners.get(record.packageName)).onDataChanged(getDataItemForRecord(record)); - } else { - - } + getListener(record.packageName, "com.google.android.gms.wearable.DATA_CHANGED", record.dataItem.uri) + .onDataChanged(record.toEventDataHolder()); } catch (RemoteException e) { Log.w(TAG, e); } @@ -402,7 +398,7 @@ public class WearableImpl { return record; } - public DataHolder getDataItems(String packageName) { + public DataHolder getDataItemsAsHolder(String packageName) { Cursor dataHolderItems = nodeDatabase.getDataItemsForDataHolder(packageName, PackageUtils.firstSignatureDigest(context, packageName)); while (dataHolderItems.moveToNext()) { Log.d(TAG, "getDataItems[]: path=" + Uri.parse(dataHolderItems.getString(1)).getPath()); @@ -412,7 +408,7 @@ public class WearableImpl { return DataHolder.fromCursor(dataHolderItems, 0, null); } - public DataHolder getDataItemsByUri(Uri uri, String packageName) { + public DataHolder getDataItemsByUriAsHolder(Uri uri, String packageName) { String firstSignature; try { firstSignature = PackageUtils.firstSignatureDigest(context, packageName); @@ -428,7 +424,7 @@ public class WearableImpl { return DataHolder.fromCursor(dataHolderItems, 0, null); } - public DataHolder getDataItemForRecord(DataItemRecord record) { + public DataHolder getDataItemForRecordAsHolder(DataItemRecord record) { Cursor dataHolderItems = nodeDatabase.getDataItemsForDataHolderByHostAndPath(record.packageName, record.signatureDigest, record.dataItem.uri.getHost(), record.dataItem.uri.getPath()); while (dataHolderItems.moveToNext()) { Log.d(TAG, "getDataItems[]: path=" + Uri.parse(dataHolderItems.getString(1)).getPath()); @@ -490,10 +486,11 @@ public class WearableImpl { return records.size(); } - public void sendMessageReceived(MessageEventParcelable messageEvent) { + public void sendMessageReceived(String packageName, MessageEventParcelable messageEvent) { Log.d(TAG, "onMessageReceived: " + messageEvent); try { - getAllListeners().onMessageReceived(messageEvent); + getListener(packageName, "com.google.android.gms.wearable.MESSAGE_RECEIVED", Uri.parse("wear://" + getLocalNodeId() + "/" + messageEvent.getPath())) + .onMessageReceived(messageEvent); } catch (RemoteException e) { Log.w(TAG, e); } @@ -510,4 +507,22 @@ public class WearableImpl { } return record; } + + private IWearableListener getListener(String packageName, String action, Uri uri) { + synchronized (this) { + List l = new ArrayList(listeners.containsKey(packageName) ? listeners.get(packageName) : Collections.emptyList()); + + Intent intent = new Intent(action); + intent.setPackage(packageName); + intent.setData(uri); + + l.add(RemoteListenerProxy.get(context, intent, IWearableListener.class, "com.google.android.gms.wearable.BIND_LISTENER")); + + return MultiListenerProxy.get(IWearableListener.class, l); + } + } + + public void sendMessage(String targetNodeId, String path, byte[] data) { + Log.d(TAG, "sendMessage not yet implemented!"); + } } diff --git a/play-services-core/src/main/java/org/microg/gms/wearable/WearableServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/wearable/WearableServiceImpl.java index 9eb02da4..2bd3a501 100644 --- a/play-services-core/src/main/java/org/microg/gms/wearable/WearableServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/wearable/WearableServiceImpl.java @@ -20,7 +20,6 @@ import android.content.Context; import android.net.Uri; import android.os.Parcel; import android.os.RemoteException; -import android.support.v7.app.NotificationCompat; import android.util.Log; import com.google.android.gms.common.api.Status; @@ -75,13 +74,19 @@ public class WearableServiceImpl extends IWearableService.Stub { @Override public void getDataItems(IWearableCallbacks callbacks) throws RemoteException { Log.d(TAG, "getDataItems: " + callbacks); - callbacks.onDataHolder(wearable.getDataItems(packageName)); + callbacks.onDataHolder(wearable.getDataItemsAsHolder(packageName)); + } + + @Override + public void sendMessage(IWearableCallbacks callbacks, String targetNodeId, String path, byte[] data) throws RemoteException { + Log.d(TAG, "sendMessage: " + targetNodeId + " / " + path); + wearable.sendMessage(targetNodeId, path, data); } @Override public void getDataItemsByUri(IWearableCallbacks callbacks, Uri uri, int i) throws RemoteException { Log.d(TAG, "getDataItemsByUri: " + uri); - callbacks.onDataHolder(wearable.getDataItemsByUri(uri, packageName)); + callbacks.onDataHolder(wearable.getDataItemsByUriAsHolder(uri, packageName)); } @Override