1
1
package com .wearconnectivity ;
2
2
3
- import android .util .Log ;
3
+ import android .webkit .MimeTypeMap ;
4
+
4
5
import com .facebook .common .logging .FLog ;
6
+ import com .facebook .react .bridge .Arguments ;
7
+ import com .facebook .react .bridge .LifecycleEventListener ;
5
8
import com .facebook .react .bridge .Promise ;
9
+ import com .facebook .react .bridge .WritableMap ;
6
10
import com .google .android .gms .tasks .Task ;
7
11
import com .google .android .gms .wearable .Asset ;
8
12
import com .google .android .gms .wearable .DataClient ;
15
19
import java .io .FileInputStream ;
16
20
import java .io .FileOutputStream ;
17
21
import java .io .IOException ;
22
+ import java .io .InputStream ;
23
+ import java .net .URLConnection ;
24
+ import java .io .BufferedInputStream ;
25
+ import java .io .IOException ;
26
+
27
+ import androidx .annotation .NonNull ;
28
+ import com .facebook .react .modules .core .DeviceEventManagerModule ;
29
+ import com .google .android .gms .tasks .OnFailureListener ;
30
+ import com .google .android .gms .tasks .OnSuccessListener ;
31
+ import com .google .android .gms .wearable .DataEvent ;
32
+ import com .google .android .gms .wearable .DataEventBuffer ;
33
+ import com .google .android .gms .wearable .DataMap ;
34
+ import com .google .android .gms .wearable .DataMapItem ;
18
35
19
- public class WearConnectivityDataClient {
36
+ public class WearConnectivityDataClient implements DataClient . OnDataChangedListener , LifecycleEventListener {
20
37
private static final String TAG = "WearConnectivityDataClient" ;
21
38
private DataClient dataClient ;
22
39
private static ReactApplicationContext reactContext ;
40
+ private String fileName = "unknown_file" ;
41
+ private long startTime ;
42
+ private int totalBytes ;
23
43
24
44
public WearConnectivityDataClient (ReactApplicationContext context ) {
25
45
dataClient = Wearable .getDataClient (context );
26
46
reactContext = context ;
47
+ dataClient .addListener (this );
48
+ context .addLifecycleEventListener (this );
27
49
}
28
50
29
51
/**
30
52
* Sends a file (as an Asset) using the DataClient API.
31
- * @param path to the file to be sent.
53
+ * @param uri path to the file to be sent.
32
54
*/
33
55
public void sendFile (String uri , Promise promise ) {
34
56
File file = new File (uri );
@@ -37,7 +59,6 @@ public void sendFile(String uri, Promise promise) {
37
59
FLog .w (TAG , "Failed to create asset from file." );
38
60
return ;
39
61
}
40
- // Create a DataMapRequest with a defined path.
41
62
PutDataMapRequest dataMapRequest = PutDataMapRequest .create ("/file_transfer" );
42
63
dataMapRequest .getDataMap ().putAsset ("file" , asset );
43
64
dataMapRequest .getDataMap ().putLong ("timestamp" , System .currentTimeMillis ());
@@ -50,6 +71,28 @@ public void sendFile(String uri, Promise promise) {
50
71
});
51
72
}
52
73
74
+ @ Override
75
+ public void onDataChanged (@ NonNull DataEventBuffer dataEvents ) {
76
+ for (DataEvent event : dataEvents ) {
77
+ if (event .getType () == DataEvent .TYPE_CHANGED ) {
78
+ DataItem item = event .getDataItem ();
79
+ if (item .getUri ().getPath ().equals ("/file_transfer" )) {
80
+ DataMap dataMap = DataMapItem .fromDataItem (item ).getDataMap ();
81
+ // Extract metadata from the DataMap
82
+ if (dataMap .containsKey ("metadata" )) {
83
+ DataMap metadata = dataMap .getDataMap ("metadata" );
84
+ fileName = metadata .getString ("fileName" , "unknown_file" );
85
+ }
86
+
87
+ Asset asset = dataMap .getAsset ("file" );
88
+ if (asset != null ) {
89
+ receiveFile (asset );
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+
53
96
/**
54
97
* Helper method to create an Asset from a file.
55
98
* @param file the file to convert.
@@ -71,4 +114,120 @@ private Asset createAssetFromFile(File file) {
71
114
private static ReactApplicationContext getReactContext () {
72
115
return reactContext ;
73
116
}
117
+
118
+ private void receiveFile (Asset asset ) {
119
+ Task <DataClient .GetFdForAssetResponse > task = dataClient .getFdForAsset (asset );
120
+ startTime = System .currentTimeMillis ();
121
+
122
+ // Dispatch 'started' event
123
+ dispatchFileTransferEvent ("started" , startTime , 0 , 0 , 0 , 0 , fileName , null );
124
+ task .addOnSuccessListener (this ::handleFileReceived )
125
+ .addOnFailureListener (this ::handleFileReceiveError );
126
+ }
127
+
128
+
129
+ /**
130
+ * Dispatches a file transfer event to React Native.
131
+ */
132
+ private void dispatchFileTransferEvent (
133
+ String type , long startTime , long completedUnitCount , long estimatedTimeRemaining ,
134
+ float fractionCompleted , long throughput , String fileName , String errorMessage ) {
135
+ WritableMap event = Arguments .createMap ();
136
+ String correctPath = "/data/data/" + getReactContext ().getPackageName () + "/files/" + fileName ;
137
+ FLog .w (TAG , "WatchFileReceived filePath: " + correctPath );
138
+ event .putString ("type" , type );
139
+ event .putString ("url" , correctPath );
140
+ event .putString ("id" , fileName );
141
+ event .putDouble ("startTime" , startTime );
142
+ event .putDouble ("endTime" , type .equals ("finished" ) ? System .currentTimeMillis () : 0 );
143
+ event .putDouble ("completedUnitCount" , completedUnitCount );
144
+ event .putDouble ("estimatedTimeRemaining" , estimatedTimeRemaining );
145
+ event .putDouble ("fractionCompleted" , fractionCompleted );
146
+ event .putDouble ("throughput" , throughput );
147
+ event .putMap ("metadata" , getFileMetadata (fileName )); // Get metadata if available
148
+ if (errorMessage != null ) {
149
+ event .putString ("error" , errorMessage );
150
+ } else {
151
+ event .putNull ("error" );
152
+ }
153
+
154
+ getReactContext ().getJSModule (DeviceEventManagerModule .RCTDeviceEventEmitter .class )
155
+ .emit ("FileTransferEvent" , event );
156
+ }
157
+
158
+ /**
159
+ * Retrieves metadata associated with a file.
160
+ */
161
+ private WritableMap getFileMetadata (String fileName ) {
162
+ WritableMap metadata = Arguments .createMap ();
163
+ metadata .putString ("fileName" , fileName );
164
+ metadata .putString ("fileType" , MimeTypeMap .getFileExtensionFromUrl (fileName ));
165
+ return metadata ;
166
+ }
167
+
168
+ private void handleFileReceived (DataClient .GetFdForAssetResponse response ) {
169
+ InputStream is = response .getInputStream ();
170
+ if (is == null ) {
171
+ FLog .w (TAG , "WatchFileReceiveError: InputStream is null" );
172
+ return ;
173
+ }
174
+
175
+ try {
176
+ File file = new File (getReactContext ().getFilesDir (), fileName );
177
+ totalBytes = response .getInputStream ().available ();
178
+
179
+ saveFile (is , file );
180
+ dispatchFileTransferEvent ("finished" , startTime , totalBytes , 0 , 1.0f , 0 , fileName , null );
181
+ } catch (IOException e ) {
182
+ dispatchFileTransferEvent ("error" , startTime , 0 , 0 , 0 , 0 , fileName , e .getMessage ());
183
+ }
184
+ }
185
+
186
+ private void handleFileReceiveError (@ NonNull Exception e ) {
187
+ dispatchFileTransferEvent ("error" , startTime , 0 , 0 , 0 , 0 , fileName , e .toString ());
188
+ }
189
+
190
+ private void dispatchEvent (String eventName , String body ) {
191
+ getReactContext ().getJSModule (DeviceEventManagerModule .RCTDeviceEventEmitter .class )
192
+ .emit (eventName , body );
193
+ }
194
+
195
+ private void saveFile (InputStream is , File file ) throws IOException {
196
+ FileOutputStream fos = new FileOutputStream (file );
197
+ byte [] buffer = new byte [1024 ];
198
+ int bytesRead ;
199
+ long completedBytes = 0 ;
200
+
201
+ while ((bytesRead = is .read (buffer )) != -1 ) {
202
+ fos .write (buffer , 0 , bytesRead );
203
+ completedBytes += bytesRead ;
204
+
205
+ // Calculate progress metrics
206
+ float fractionCompleted = (float ) completedBytes / totalBytes ;
207
+ long elapsedTime = System .currentTimeMillis () - startTime ;
208
+ long estimatedTimeRemaining = (long ) ((1 - fractionCompleted ) * elapsedTime / fractionCompleted );
209
+ long throughput = completedBytes * 8 / (elapsedTime + 1 ); // Avoid division by zero
210
+
211
+ // Dispatch 'progress' event
212
+ dispatchFileTransferEvent ("progress" , startTime , completedBytes , estimatedTimeRemaining , fractionCompleted , throughput , fileName , null );
213
+ }
214
+ fos .flush ();
215
+ fos .close ();
216
+ is .close ();
217
+ }
218
+
219
+ @ Override
220
+ public void onHostResume () {
221
+ // do nothing
222
+ }
223
+
224
+ @ Override
225
+ public void onHostPause () {
226
+ // do nothing
227
+ }
228
+
229
+ @ Override
230
+ public void onHostDestroy () {
231
+ dataClient .removeListener (this );
232
+ }
74
233
}
0 commit comments