mirror of
https://github.com/YTVanced/VancedMicroG
synced 2024-11-24 04:05:13 +00:00
Add (incomplete) handling of Android Wear Assets
This commit is contained in:
parent
51b8d384a1
commit
375004891e
4 changed files with 158 additions and 28 deletions
|
@ -27,8 +27,12 @@ import com.google.android.gms.wearable.internal.DataItemParcelable;
|
||||||
import org.microg.wearable.proto.AssetEntry;
|
import org.microg.wearable.proto.AssetEntry;
|
||||||
import org.microg.wearable.proto.SetDataItem;
|
import org.microg.wearable.proto.SetDataItem;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
public class DataItemRecord {
|
public class DataItemRecord {
|
||||||
public DataItemInternal dataItem;
|
public DataItemInternal dataItem;
|
||||||
public String source;
|
public String source;
|
||||||
|
@ -67,6 +71,30 @@ public class DataItemRecord {
|
||||||
return parcelable;
|
return parcelable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SetDataItem toSetDataItem() {
|
||||||
|
SetDataItem.Builder builder = new SetDataItem.Builder()
|
||||||
|
.packageName(packageName)
|
||||||
|
.signatureDigest(signatureDigest)
|
||||||
|
.uri(dataItem.uri.toString())
|
||||||
|
.seqId(seqId)
|
||||||
|
.deleted(deleted)
|
||||||
|
.lastModified(lastModified);
|
||||||
|
if (source != null) builder.source(source);
|
||||||
|
if (dataItem.data != null) builder.data(ByteString.of(dataItem.data));
|
||||||
|
List<AssetEntry> protoAssets = new ArrayList<AssetEntry>();
|
||||||
|
Map<String, Asset> assets = dataItem.getAssets();
|
||||||
|
for (String key : assets.keySet()) {
|
||||||
|
protoAssets.add(new AssetEntry.Builder()
|
||||||
|
.key(key)
|
||||||
|
.unknown3(4)
|
||||||
|
.value(new org.microg.wearable.proto.Asset.Builder()
|
||||||
|
.digest(assets.get(key).getDigest())
|
||||||
|
.build()).build());
|
||||||
|
}
|
||||||
|
builder.assets(protoAssets);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
public static DataItemRecord fromCursor(Cursor cursor) {
|
public static DataItemRecord fromCursor(Cursor cursor) {
|
||||||
DataItemRecord record = new DataItemRecord();
|
DataItemRecord record = new DataItemRecord();
|
||||||
record.packageName = cursor.getString(1);
|
record.packageName = cursor.getString(1);
|
||||||
|
@ -78,6 +106,15 @@ public class DataItemRecord {
|
||||||
record.dataItem.data = cursor.getBlob(8);
|
record.dataItem.data = cursor.getBlob(8);
|
||||||
record.lastModified = cursor.getLong(9);
|
record.lastModified = cursor.getLong(9);
|
||||||
record.assetsAreReady = cursor.getLong(10) > 0;
|
record.assetsAreReady = cursor.getLong(10) > 0;
|
||||||
|
if (cursor.getString(11) != null) {
|
||||||
|
record.dataItem.addAsset(cursor.getString(11), Asset.createFromRef(cursor.getString(12)));
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
if (cursor.getLong(5) == record.seqId) {
|
||||||
|
record.dataItem.addAsset(cursor.getString(11), Asset.createFromRef(cursor.getString(12)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.moveToPrevious();
|
||||||
|
}
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +124,7 @@ public class DataItemRecord {
|
||||||
if (setDataItem.data != null) record.dataItem.data = setDataItem.data.toByteArray();
|
if (setDataItem.data != null) record.dataItem.data = setDataItem.data.toByteArray();
|
||||||
if (setDataItem.assets != null) {
|
if (setDataItem.assets != null) {
|
||||||
for (AssetEntry asset : setDataItem.assets) {
|
for (AssetEntry asset : setDataItem.assets) {
|
||||||
// TODO record.dataItem.addAsset(asset.key, asset.value)
|
record.dataItem.addAsset(asset.key, Asset.createFromRef(asset.value.digest));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
record.source = setDataItem.source;
|
record.source = setDataItem.source;
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.microg.gms.wearable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.android.gms.wearable.Asset;
|
||||||
import com.google.android.gms.wearable.ConnectionConfiguration;
|
import com.google.android.gms.wearable.ConnectionConfiguration;
|
||||||
import com.google.android.gms.wearable.internal.MessageEventParcelable;
|
import com.google.android.gms.wearable.internal.MessageEventParcelable;
|
||||||
import com.google.android.gms.wearable.internal.NodeParcelable;
|
import com.google.android.gms.wearable.internal.NodeParcelable;
|
||||||
|
@ -86,6 +87,13 @@ public class MessageHandler extends ServerMessageListener {
|
||||||
@Override
|
@Override
|
||||||
public void onSetAsset(SetAsset setAsset) {
|
public void onSetAsset(SetAsset setAsset) {
|
||||||
Log.d(TAG, "onSetAsset: " + setAsset);
|
Log.d(TAG, "onSetAsset: " + setAsset);
|
||||||
|
Asset asset;
|
||||||
|
if (setAsset.data != null) {
|
||||||
|
asset = Asset.createFromBytes(setAsset.data.toByteArray());
|
||||||
|
} else {
|
||||||
|
asset = Asset.createFromRef(setAsset.digest);
|
||||||
|
}
|
||||||
|
service.addAssetToDatabase(asset, setAsset.appkeys.appKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -149,6 +157,7 @@ public class MessageHandler extends ServerMessageListener {
|
||||||
@Override
|
@Override
|
||||||
public void onFilePiece(FilePiece filePiece) {
|
public void onFilePiece(FilePiece filePiece) {
|
||||||
Log.d(TAG, "onFilePiece: " + filePiece);
|
Log.d(TAG, "onFilePiece: " + filePiece);
|
||||||
|
service.handleFilePiece(getConnection(), filePiece.fileName, filePiece.piece.toByteArray(), filePiece.finalPiece ? filePiece.digest : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -143,7 +143,7 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateRecord(SQLiteDatabase db, String key, DataItemRecord record) {
|
private static void updateRecord(SQLiteDatabase db, String key, DataItemRecord record) {
|
||||||
Log.d(TAG, "updateRecord not implemented: " + record);
|
Log.d(TAG, "updateRecord no: " + record);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String insertRecord(SQLiteDatabase db, DataItemRecord record) {
|
private String insertRecord(SQLiteDatabase db, DataItemRecord record) {
|
||||||
|
@ -220,8 +220,24 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
res = cursor.getLong(0);
|
res = cursor.getLong(0);
|
||||||
}
|
}
|
||||||
cursor.close();;
|
cursor.close();
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void putAsset(Asset asset, boolean dataPresent) {
|
||||||
|
ContentValues cv = new ContentValues();
|
||||||
|
cv.put("digest", asset.getDigest());
|
||||||
|
cv.put("dataPresent", dataPresent ? 1 : 0);
|
||||||
|
cv.put("timestampMs", System.currentTimeMillis());
|
||||||
|
getWritableDatabase().insert("assets", null, cv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void allowAssetAccess(String digest, String packageName, String signatureDigest) {
|
||||||
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
|
ContentValues cv = new ContentValues();
|
||||||
|
cv.put("assets_digest", digest);
|
||||||
|
cv.put("appkeys_it", getAppKey(db, packageName, signatureDigest));
|
||||||
|
db.insert("assetsacls", null, cv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,17 +53,23 @@ import com.google.android.gms.wearable.internal.PutDataResponse;
|
||||||
import com.google.android.gms.wearable.internal.RemoveListenerRequest;
|
import com.google.android.gms.wearable.internal.RemoveListenerRequest;
|
||||||
|
|
||||||
import org.microg.gms.common.PackageUtils;
|
import org.microg.gms.common.PackageUtils;
|
||||||
|
import org.microg.gms.common.Utils;
|
||||||
import org.microg.wearable.WearableConnection;
|
import org.microg.wearable.WearableConnection;
|
||||||
import org.microg.wearable.proto.AssetEntry;
|
import org.microg.wearable.proto.AckAsset;
|
||||||
|
import org.microg.wearable.proto.AppKey;
|
||||||
|
import org.microg.wearable.proto.AppKeys;
|
||||||
|
import org.microg.wearable.proto.FilePiece;
|
||||||
import org.microg.wearable.proto.RootMessage;
|
import org.microg.wearable.proto.RootMessage;
|
||||||
import org.microg.wearable.proto.SetDataItem;
|
import org.microg.wearable.proto.SetAsset;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -129,12 +135,13 @@ public class WearableServiceImpl extends IWearableService.Stub implements IWeara
|
||||||
for (Map.Entry<String, Asset> assetEntry : request.getAssets().entrySet()) {
|
for (Map.Entry<String, Asset> assetEntry : request.getAssets().entrySet()) {
|
||||||
Asset asset = prepareAsset(packageName, assetEntry.getValue());
|
Asset asset = prepareAsset(packageName, assetEntry.getValue());
|
||||||
if (asset != null) {
|
if (asset != null) {
|
||||||
|
nodeDatabase.putAsset(asset, true);
|
||||||
dataItem.addAsset(assetEntry.getKey(), asset);
|
dataItem.addAsset(assetEntry.getKey(), asset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataItem.data = request.getData();
|
dataItem.data = request.getData();
|
||||||
DataItemParcelable parcelable = putDataItem(packageName,
|
DataItemParcelable parcelable = putDataItem(packageName, PackageUtils.firstSignatureDigest(context, packageName),
|
||||||
PackageUtils.firstSignatureDigest(context, packageName), getLocalNodeId(), dataItem).toParcelable();
|
getLocalNodeId(), dataItem).toParcelable();
|
||||||
callbacks.onPutDataResponse(new PutDataResponse(0, parcelable));
|
callbacks.onPutDataResponse(new PutDataResponse(0, parcelable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +163,13 @@ public class WearableServiceImpl extends IWearableService.Stub implements IWeara
|
||||||
}
|
}
|
||||||
|
|
||||||
private Asset prepareAsset(String packageName, Asset asset) {
|
private Asset prepareAsset(String packageName, Asset asset) {
|
||||||
|
if (asset.getFd() != null && asset.data == null) {
|
||||||
|
try {
|
||||||
|
asset.data = Utils.readStreamToEnd(new FileInputStream(asset.getFd().getFileDescriptor()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (asset.data != null) {
|
if (asset.data != null) {
|
||||||
String digest = calculateDigest(asset.data);
|
String digest = calculateDigest(asset.data);
|
||||||
File assetFile = createAssetFile(digest);
|
File assetFile = createAssetFile(digest);
|
||||||
|
@ -188,6 +202,12 @@ public class WearableServiceImpl extends IWearableService.Stub implements IWeara
|
||||||
return new File(dir, digest + ".asset");
|
return new File(dir, digest + ".asset");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File createAssetReceiveTempFile(String name) {
|
||||||
|
File dir = new File(context.getFilesDir(), "piece");
|
||||||
|
dir.mkdirs();
|
||||||
|
return new File(dir, name);
|
||||||
|
}
|
||||||
|
|
||||||
private String calculateDigest(byte[] data) {
|
private String calculateDigest(byte[] data) {
|
||||||
try {
|
try {
|
||||||
return Base64.encodeToString(MessageDigest.getInstance("SHA1").digest(data), Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
|
return Base64.encodeToString(MessageDigest.getInstance("SHA1").digest(data), Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
|
||||||
|
@ -480,29 +500,13 @@ public class WearableServiceImpl extends IWearableService.Stub implements IWeara
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
DataItemRecord record = DataItemRecord.fromCursor(cursor);
|
DataItemRecord record = DataItemRecord.fromCursor(cursor);
|
||||||
Log.d(TAG, "Sync over " + connection + ": " + record);
|
for (Asset asset : record.dataItem.getAssets().values()) {
|
||||||
SetDataItem.Builder builder = new SetDataItem.Builder()
|
syncAssetToPeer(connection, record, asset);
|
||||||
.packageName(record.packageName)
|
|
||||||
.signatureDigest(record.signatureDigest)
|
|
||||||
.uri(record.dataItem.uri.toString())
|
|
||||||
.seqId(record.seqId)
|
|
||||||
.deleted(record.deleted)
|
|
||||||
.lastModified(record.lastModified);
|
|
||||||
if (record.source != null) builder.source(record.source);
|
|
||||||
if (record.dataItem.data != null) builder.data(ByteString.of(record.dataItem.data));
|
|
||||||
List<AssetEntry> protoAssets = new ArrayList<AssetEntry>();
|
|
||||||
Map<String, Asset> assets = record.dataItem.getAssets();
|
|
||||||
for (String key : assets.keySet()) {
|
|
||||||
protoAssets.add(new AssetEntry.Builder()
|
|
||||||
.key(key)
|
|
||||||
.unknown3(4)
|
|
||||||
.value(new org.microg.wearable.proto.Asset.Builder()
|
|
||||||
.digest(assets.get(key).getDigest())
|
|
||||||
.build()).build());
|
|
||||||
}
|
}
|
||||||
builder.assets(protoAssets);
|
Log.d(TAG, "Sync over " + connection + ": " + record);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
connection.writeMessage(new RootMessage.Builder().setDataItem(builder.build()).build());
|
connection.writeMessage(new RootMessage.Builder().setDataItem(record.toSetDataItem()).build());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
break;
|
break;
|
||||||
|
@ -513,7 +517,71 @@ public class WearableServiceImpl extends IWearableService.Stub implements IWeara
|
||||||
Log.d(TAG, "-- Done syncing over " + connection + ", nodeId " + nodeId + " starting with seqId " + seqId);
|
Log.d(TAG, "-- Done syncing over " + connection + ", nodeId " + nodeId + " starting with seqId " + seqId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void syncAssetToPeer(WearableConnection connection, DataItemRecord record, Asset asset) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Sync over " + connection + ": " + asset);
|
||||||
|
connection.writeMessage(new RootMessage.Builder().setAsset(
|
||||||
|
new SetAsset.Builder()
|
||||||
|
.digest(asset.getDigest())
|
||||||
|
.appkeys(new AppKeys(Collections.singletonList(new AppKey(record.packageName, record.signatureDigest))))
|
||||||
|
.build()).unknown13(true).build());
|
||||||
|
File assetFile = createAssetFile(asset.getDigest());
|
||||||
|
String fileName = calculateDigest(assetFile.toString().getBytes());
|
||||||
|
FileInputStream fis = new FileInputStream(assetFile);
|
||||||
|
byte[] arr = new byte[12215];
|
||||||
|
ByteString lastPiece = null;
|
||||||
|
int c = 0;
|
||||||
|
while ((c = fis.read(arr)) > 0) {
|
||||||
|
if (lastPiece != null) {
|
||||||
|
Log.d(TAG, "Sync over " + connection + ": Asset piece for fileName " + fileName + ": " + lastPiece);
|
||||||
|
connection.writeMessage(new RootMessage.Builder().filePiece(new FilePiece(fileName, false, lastPiece, null)).build());
|
||||||
|
}
|
||||||
|
lastPiece = ByteString.of(arr, 0, c);
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Sync over " + connection + ": Last asset piece for fileName " + fileName + ": " + lastPiece);
|
||||||
|
connection.writeMessage(new RootMessage.Builder().filePiece(new FilePiece(fileName, true, lastPiece, asset.getDigest())).build());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAssetToDatabase(Asset asset, List<AppKey> appKeys) {
|
||||||
|
nodeDatabase.putAsset(asset, false);
|
||||||
|
for (AppKey appKey : appKeys) {
|
||||||
|
nodeDatabase.allowAssetAccess(asset.getDigest(), appKey.packageName, appKey.signatureDigest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long getCurrentSeqId(String nodeId) {
|
public long getCurrentSeqId(String nodeId) {
|
||||||
return nodeDatabase.getCurrentSeqId(nodeId);
|
return nodeDatabase.getCurrentSeqId(nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleFilePiece(WearableConnection connection, String fileName, byte[] bytes, String finalPieceDigest) {
|
||||||
|
File file = createAssetReceiveTempFile(fileName);
|
||||||
|
try {
|
||||||
|
FileOutputStream fos = new FileOutputStream(file, true);
|
||||||
|
fos.write(bytes);
|
||||||
|
fos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
if (finalPieceDigest != null) {
|
||||||
|
// This is a final piece. If digest matches we're so happy!
|
||||||
|
try {
|
||||||
|
String digest = calculateDigest(Utils.readStreamToEnd(new FileInputStream(file)));
|
||||||
|
if (digest.equals(finalPieceDigest)) {
|
||||||
|
if (file.renameTo(createAssetFile(digest))) {
|
||||||
|
// TODO: Mark as stored in db
|
||||||
|
connection.writeMessage(new RootMessage.Builder().ackAsset(new AckAsset(digest)).build());
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Could not rename to target file name. delete=" + file.delete());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Received digest does not match. delete=" + file.delete());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed working with temp file. delete=" + file.delete(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue