Wearable: correctly implement listeners including remote binding

This commit is contained in:
Marvin W 2016-07-31 13:01:27 +02:00
parent 8cb49f7c98
commit 27c1c540d6
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
6 changed files with 201 additions and 20 deletions

View File

@ -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<T extends IInterface> 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<Runnable> waiting = new ArrayList<Runnable>();
private Class<T> tClass;
public static <T extends IInterface> T get(Context context, Intent intent, Class<T> tClass, String bindAction) {
return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass},
new RemoteListenerProxy<T>(context, intent, tClass, bindAction));
}
private RemoteListenerProxy(Context context, Intent intent, Class<T> 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;
}
}

View File

@ -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<String, Object> data = new HashMap<String, Object>();
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<String, Asset> 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<String, Object>();
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;

View File

@ -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 {

View File

@ -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);

View File

@ -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<IWearableListener> l = new ArrayList<IWearableListener>(listeners.containsKey(packageName) ? listeners.get(packageName) : Collections.<IWearableListener>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!");
}
}

View File

@ -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