Imported Upstream version 0.9.2
[platform/upstream/iotivity.git] / android / examples / guiclient / src / main / java / org / iotivity / guiclient / OcWorker.java
1 //
2 // Copyright 2014 Intel Corporation.
3 //
4 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
5 //
6 // Licensed under the Apache License, Version 2.0 (the "License");
7 // you may not use this file except in compliance with the License.
8 // You may obtain a copy of the License at
9 //
10 //      http://www.apache.org/licenses/LICENSE-2.0
11 //
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17 //
18 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
19
20 package org.iotivity.guiclient;
21
22 import android.content.Context;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.util.Log;
27
28 import org.iotivity.base.ModeType;
29 import org.iotivity.base.OcConnectivityType;
30 import org.iotivity.base.OcException;
31 import org.iotivity.base.OcPlatform;
32 import org.iotivity.base.OcResource;
33 import org.iotivity.base.PlatformConfig;
34 import org.iotivity.base.QualityOfService;
35 import org.iotivity.base.ServiceType;
36
37 import java.util.ArrayList;
38 import java.util.EnumSet;
39 import java.util.List;
40
41 import static org.iotivity.guiclient.OcProtocolStrings.COAP_CORE;
42 import static org.iotivity.guiclient.OcProtocolStrings.CORE_EDISON_RESOURCES;
43 import static org.iotivity.guiclient.OcProtocolStrings.CORE_LIGHT;
44 import static org.iotivity.guiclient.OcProtocolStrings.INTERFACE_QUERY;
45 import static org.iotivity.guiclient.OcProtocolStrings.RESOURCE_TYPE_QUERY;
46 import static org.iotivity.guiclient.OcWorker.OCW_IN_MSG.DO_CLEAR_RESOURCES;
47 import static org.iotivity.guiclient.OcWorker.OCW_IN_MSG.DO_DISCOVER_RESOURCES;
48 import static org.iotivity.guiclient.OcWorker.OCW_IN_MSG.DO_GET_RESOURCE;
49 import static org.iotivity.guiclient.OcWorker.OCW_IN_MSG.DO_PUT_RESOURCE;
50 import static org.iotivity.guiclient.OcWorker.OCW_IN_MSG.fromInt;
51
52 /**
53  * OcWorker
54  *
55  * A class designed to encapsulate the OIC API functionality.  OcWorker has its own
56  * thread which is used to call all OIC APIs.  To get results back from OcWorker,
57  * implement the interface OcWorkerListener, and call registerListener().
58  *
59  * @see org.iotivity.guiclient.OcWorkerListener
60  *
61  * Created by nathanhs on 12/22/15.
62  */
63 public class OcWorker extends Thread
64         implements OcPlatform.OnResourceFoundListener, OcResourceInfo.OcResourceInfoListener  {
65     /**
66      * Hardcoded TAG... if project never uses proguard then
67      * MyOcClient.class.getName() is the better way.
68      */
69     private static final String TAG = "OcWorker";
70     private Context mContext;
71
72     private static final boolean LOCAL_LOGV = true; // set to false to compile out verbose logging
73
74     /**
75      * NOTE: DO NOT assign non-default values to these enums!
76      * The correctness of "fromInt()" depends on the enum values not being
77      * overridden.  For example DO_TEST = 100, RESULT_TEST = 101.. would BREAK the
78      * fromInt() function.  There are designs which can account for arbitrary enum
79      * values, but they are less desirable for other reasons and since we do not
80      * need to use any specific value, this design is the best for this case.
81      */
82
83     /**
84      * These "IN" message types are for posting API-generated request actions
85      * to our OcWorker queue.
86      */
87     public enum OCW_IN_MSG {
88         DO_TEST,  // developer testing only
89         DO_DISCOVER_RESOURCES,
90         DO_CLEAR_RESOURCES,
91         DO_GET_RESOURCE,
92         DO_PUT_RESOURCE,
93         DO_OBSERVE_RESOURCE,
94         DO_STOP_OBSERVING_RESOURCE;
95
96         private static final OCW_IN_MSG[] values = values();
97
98         public static OCW_IN_MSG fromInt(int i) {
99             return values[i];
100         }
101     }
102
103     /**
104      * These events are for internally putting work on our thread's looper
105      * queue, usually in a callback where we don't want to do work on the
106      * callback thread.
107      */
108     private enum OC_EVENT {
109         OIC_RESOURCE_FOUND,
110         OIC_RESOURCE_CHANGED;
111
112         private static final OC_EVENT[] values = OC_EVENT.values();
113
114         public static OC_EVENT fromInt(int i) {
115             return values[i];
116         }
117     }
118
119     private Handler mDoMsgHandler;
120
121     private Handler mOcEventHandler;
122
123     /**
124      * The OcResourceInfo List
125      */
126     private ArrayList<OcResourceInfo> mOcResourceInfoList;
127
128     /**
129      * The types of OIC Resources included in "FindResource" calls by this object.
130      */
131     private final String[] mOcFindQueries = {
132             COAP_CORE + RESOURCE_TYPE_QUERY + CORE_LIGHT,
133             COAP_CORE + INTERFACE_QUERY + CORE_EDISON_RESOURCES
134     };
135
136     private List<OcWorkerListener> mListeners;
137
138     public OcWorker(Context context) {
139         if (LOCAL_LOGV) Log.v(TAG, "OcWorker() constructor");
140
141         mContext = context;
142         this.mListeners = new ArrayList<>();
143     }
144
145     /**
146      * Set up our Handler and Looper, then initialize the OIC platform and
147      * start processing messages as they arrive.
148      */
149     public void run() {
150         if (LOCAL_LOGV) Log.v(TAG, "run()");
151
152         Looper.prepare();
153         this.initHandlers(); // set up our message handler
154         this.ocInit(); // init the OIC layer including calling ConfigurePlatform
155         Looper.loop();
156     }
157
158     /**
159      * Registers a listener for OcWorker events.
160      *
161      * @see org.iotivity.guiclient.OcWorkerListener
162      */
163     public void registerListener(OcWorkerListener listener) {
164         if (LOCAL_LOGV) Log.v(TAG, "registerListener()");
165
166         if(null != this.mListeners) {
167             this.mListeners.add(listener);
168         } else {
169             Log.e(TAG, "registerListener(): null mListeners list; not adding listener!");
170             Log.e(TAG, "OcWorker.run() must be called before using public methods.");
171         }
172     }
173
174     /**
175      * The Resource discovery external API
176      */
177     public void doDiscoverResources() {
178         if (LOCAL_LOGV) Log.v(TAG, "doDiscoverResources()");
179
180         if(null != this.mDoMsgHandler) {
181             this.mDoMsgHandler.obtainMessage(
182                     DO_DISCOVER_RESOURCES.ordinal()).sendToTarget();
183         } else {
184             Log.e(TAG, "doDiscoverResources(): null mDoMsgHandler; not discovering resources!");
185             Log.e(TAG, "OcWorker.run() must be called before using public methods.");
186         }
187     }
188
189     /**
190      * The GetResource external API
191      */
192     public void doGetResource(OcResourceInfo resourceInfo) {
193         if (LOCAL_LOGV) Log.v(TAG, "doGetResource()");
194
195         if(null != this.mDoMsgHandler) {
196             this.mDoMsgHandler.obtainMessage(
197                     DO_GET_RESOURCE.ordinal(), resourceInfo).sendToTarget();
198         } else {
199             Log.e(TAG, "doPutResource(): null mDoMsgHandler; not putting resource!");
200             Log.e(TAG, "OcWorker.run() must be called before using public methods.");
201         }
202     }
203
204     /**
205      * The PutResource external API
206      */
207     public void doPutResource(OcResourceInfo resourceInfo) {
208         if (LOCAL_LOGV) Log.v(TAG, "doPutResource()");
209
210         if(null != this.mDoMsgHandler) {
211             this.mDoMsgHandler.obtainMessage(
212                     DO_PUT_RESOURCE.ordinal(), resourceInfo).sendToTarget();
213         } else {
214             Log.e(TAG, "doPutResource(): null mDoMsgHandler; not putting resource!");
215             Log.e(TAG, "OcWorker.run() must be called before using public methods.");
216         }
217     }
218
219     /**
220      * The Clear Resources external API
221      */
222     public void doClearResources() {
223         if (LOCAL_LOGV) Log.v(TAG, "doClearResources()");
224
225         if(null != this.mDoMsgHandler) {
226             this.mDoMsgHandler.obtainMessage(
227                     DO_CLEAR_RESOURCES.ordinal()).sendToTarget();
228         } else {
229             Log.e(TAG, "doClearResources(): null mDoMsgHandler; not clearing resources!");
230             Log.e(TAG, "OcWorker.run() must be called before using public methods.");
231         }
232     }
233
234     /**
235      * Set up handlers
236      */
237     private void initHandlers() {
238         if (LOCAL_LOGV) Log.v(TAG, "initHandler()");
239
240         this.mDoMsgHandler = new Handler() {
241             public void handleMessage(Message msg) {
242                 Log.d(TAG, String.format("mDoMsgHandler.handleMessage(%s)", msg.toString()));
243                 // process incoming messages here
244                 OCW_IN_MSG type = fromInt(msg.what);
245                 switch(type) {
246                     case DO_TEST:
247                         break;
248                     case DO_DISCOVER_RESOURCES:
249                         discoverResources();
250                         break;
251                     case DO_CLEAR_RESOURCES:
252                         clearResourceInfoList();
253                         break;
254                     case DO_GET_RESOURCE:
255                         getResourceAttributes((OcResourceInfo)msg.obj);
256                         break;
257                     case DO_PUT_RESOURCE:
258                         putResourceAttributes((OcResourceInfo)msg.obj);
259                         break;
260                     case DO_OBSERVE_RESOURCE:
261                         break;
262                     case DO_STOP_OBSERVING_RESOURCE:
263                         break;
264                     default:
265                         Log.e(TAG, "unknown msg.what in handler");
266                         break;
267                 }
268             }
269         };
270
271         this.mOcEventHandler = new Handler() {
272             public void handleMessage(Message msg) {
273                 Log.d(TAG, String.format("mOcEventHandler.handleMessage(%s)", msg.toString()));
274                 // process incoming messages here
275                 OC_EVENT type = OC_EVENT.fromInt(msg.what);
276                 switch(type) {
277                     case OIC_RESOURCE_FOUND:
278                         handleNewResourceFound((OcResource)msg.obj);
279                         break;
280                     case OIC_RESOURCE_CHANGED:
281                         handleResourceInfoChange((OcResourceInfo)msg.obj);
282                         break;
283                 }
284             }
285         };
286     }
287
288     /**
289      * Get the attributes on resourceInfo.
290      *
291      * @param resourceInfo
292      */
293     private void getResourceAttributes(OcResourceInfo resourceInfo) {
294         if (LOCAL_LOGV) Log.v(TAG, "getResourceAttributes()");
295
296         // find the matching resource in our resourceList
297         OcResourceInfo existingResource = this.selectResourceInfoByHostAndUri(
298                 resourceInfo.getHost() + resourceInfo.getUri());
299         if(null != existingResource) {
300             existingResource.doOcGet();
301         } else {
302             Log.e(TAG, "getResourceAttributes(): could not find target resource.");
303         }
304         // Done.  Later, the onGet listener in the OcResourceInfo object will notify us of a change
305         // via our onResourceChanged() method
306     }
307
308     /**
309      * For each attribute in the resourceInfo.mAttributes, put the attribute value to the
310      * Resource.
311      *
312      * @param resourceInfo
313      */
314     private void putResourceAttributes(OcResourceInfo resourceInfo) {
315         if (LOCAL_LOGV) Log.v(TAG, "putResourceAttributes()");
316
317         // find the matching resource in our resourceList
318         OcResourceInfo existingResource = this.selectResourceInfoByHostAndUri(
319                 resourceInfo.getHost() + resourceInfo.getUri());
320         if(null != existingResource) {
321             // for each attribute in resourceInfo, put that attribute to the resource
322             for(OcAttributeInfo attribute : resourceInfo.getAttributes()) {
323                 if(false == attribute.isReadOnly()) {
324                     existingResource.doOcPut(attribute);
325                 }
326             }
327         } else {
328             Log.e(TAG, "putResourceAttributes(): could not find target resource.");
329         }
330         // Done. later, the onPut listener in the OcResourceInfo object will notify us of a change
331         // via our onResourceChanged() method
332     }
333
334     /**
335      * Because this callback is called on a JNI layer thread, don't do work here.
336      * Instead, create a "found resource" message and send to OcWorker's message queue,
337      * Our looper/handler then calls handleNewResource on our own worker thread.
338      *
339      * Also note that this method must be thread safe because it can be called by
340      * multiple concurrent native threads.
341      *
342      * @param resource
343      */
344     private Object onResourceFoundLock = new Object(); // not strictly necessary with this impl.,
345                                                        // but clears up Log message readability.
346     @Override
347     public void onResourceFound(OcResource resource) {
348         synchronized (onResourceFoundLock) {
349             if (LOCAL_LOGV) Log.v(TAG, "onResourceFound()");
350             if (LOCAL_LOGV) Log.v(TAG, "host: " + resource.getHost());
351             if (LOCAL_LOGV) Log.v(TAG, "uri: " + resource.getUri());
352             if (LOCAL_LOGV) Log.v(TAG, "is observable: " + resource.isObservable());
353
354             this.mOcEventHandler.obtainMessage(OC_EVENT.OIC_RESOURCE_FOUND.ordinal(),
355                     resource).sendToTarget();
356         }
357     }
358
359     /**
360      * Handles the internal NEW_RESOURCE_FOUND event, typically engueued on "onResourceFound".
361      * Creates a new OcResourceInfo object to wrap the new OcResource and store other info.
362      *
363      * @param resource the OcResource object
364      */
365     private void handleNewResourceFound(OcResource resource) {
366         if (LOCAL_LOGV) Log.v(TAG, String.format("handleNewResourceFound(%s)",
367                 resource.toString()));
368
369         OcResourceInfo ri =
370                 this.selectResourceInfoByHostAndUri(resource.getHost() + resource.getUri());
371
372         // before notifying listeners, update our own internal OcResourceInfo list
373         if(null != mOcResourceInfoList) {
374             // check for pre-existing duplicate before adding
375             if(null == ri) {
376                 if (LOCAL_LOGV) Log.v(TAG, "handleNewResourceFound(): ri is new; adding.");
377                 // if not found, create new info object
378                 ri = new OcResourceInfo(resource, this);
379                 // register as a listener to the newly created OcResourceInfo
380                 ri.registerListener(this);
381                 // kick off a get to fill in attributes
382                 ri.doOcGet();
383                 // add the info object to our list
384                 mOcResourceInfoList.add(ri);
385             }
386         }
387         // notify listeners
388         for(OcWorkerListener l : this.mListeners) {
389             l.onResourceFound(ri);
390         }
391     }
392
393     /**
394      * The "listener" callback from the OcResourceInfo class.
395      * Called by the OcResourceInfo object using the native callback thread.
396      * We use this callback to post an event to our queue so that the work
397      * is serialized with other incoming events, and executed on our worker thread.
398      *
399      * Also note that this method must be thread safe because it could be called by
400      * one of many OcResourceInfo objects on separate native threads.
401      *
402      * @param resourceInfo
403      */
404     private Object onResourceInfoChangedLock = new Object();
405     @Override
406     public void onResourceInfoChanged(OcResourceInfo resourceInfo) {
407
408         synchronized (onResourceInfoChangedLock) {
409             if (LOCAL_LOGV) Log.v(TAG, String.format("resourceInfoChanged(%s)",
410                     resourceInfo.toString()));
411
412             // this is a result of a callback (i.e. onGetCompleted, onPut, onObserve)
413             // so we post a message to our queue to transfer the work to our own thread
414             this.mOcEventHandler.obtainMessage(OC_EVENT.OIC_RESOURCE_CHANGED.ordinal(),
415                     resourceInfo).sendToTarget();
416         }
417     }
418
419     /**
420      * Handle our internal event that is enqueued when a resource is found.
421      *
422      * @param resourceInfo
423      */
424     private void handleResourceInfoChange(OcResourceInfo resourceInfo) {
425         if (LOCAL_LOGV) Log.v(TAG, "handleResourceInfoChange()");
426
427         // notify listeners
428         for(OcWorkerListener l : this.mListeners) {
429             l.onResourceChanged(resourceInfo);
430         }
431     }
432
433     /**
434      * Complete OIC-related initialization, including configuring the platform
435      */
436     private void ocInit() {
437         if (LOCAL_LOGV) Log.v(TAG, "ocInit()");
438
439         // OIC initialization
440         mOcResourceInfoList = new ArrayList<>();
441
442         this.configurePlatform();
443     }
444
445     /**
446      * Configures the OIC platform.
447      */
448     private void configurePlatform() {
449         if (LOCAL_LOGV) Log.v(TAG, "configurePlatform()");
450
451         PlatformConfig cfg = new PlatformConfig(
452                 mContext,
453                 ServiceType.IN_PROC,
454                 ModeType.CLIENT_SERVER,
455                 "0.0.0.0", // bind to all available interfaces
456                 0,
457                 QualityOfService.LOW);
458
459         Log.d(TAG, "configurePlatform(): calling OcPlatform.Configure()");
460         OcPlatform.Configure(cfg);
461     }
462
463     /**
464      * Search mOcResourceInfo list for a resource whose Host and Uri concatenated
465      * matches the param passed, and return it.
466      *
467      * @param resourceHostAndUri
468      * @return OcResourceInfo with Host and Uri matching resourceHostAndUri, or null if
469      * no such OcResourceInfo exists in mOcResourceInfoList
470      */
471     private OcResourceInfo selectResourceInfoByHostAndUri(String resourceHostAndUri) {
472         if (LOCAL_LOGV) Log.v(TAG, "selectResourceByHostAndUri()");
473
474         boolean found = false;
475         OcResourceInfo retVal = null;
476
477         for(OcResourceInfo ri : mOcResourceInfoList) {
478             if(!found) {
479                 String s = ri.getHost() + ri.getUri();
480                 if (resourceHostAndUri.equalsIgnoreCase(s)) {
481                     retVal = ri;
482                     found = true;
483                 }
484             }
485         }
486         if(!found) {
487             Log.v(TAG, "selectResourceByHostAndUri(): no resource found matching HostAndUri "
488                     + resourceHostAndUri);
489         }
490
491         return retVal;
492     }
493
494     /**
495      * Finds OIC Resources matching known patterns.
496      *
497      * @see org.iotivity.guiclient.OcProtocolStrings
498      */
499     private void discoverResources() {
500         if (LOCAL_LOGV) Log.v(TAG, "discoverResources()");
501
502         try {
503             for (String s : mOcFindQueries) {
504                 Log.d(TAG, String.format("discoverResources(): Calling OcPlatform.findResource(%s)", s));
505                 OcPlatform.findResource("",
506                         OcPlatform.WELL_KNOWN_QUERY + "?rt=" + s,
507                         EnumSet.of(OcConnectivityType.CT_DEFAULT),
508                         this);
509             }
510         } catch (OcException e) {
511             e.printStackTrace();
512             Log.e(TAG, e.getMessage());
513         }
514     }
515
516     /**
517      * Clear the ResourceInfoList
518      */
519     private void clearResourceInfoList() {
520         if (LOCAL_LOGV) Log.v(TAG, "clearResourceInfoList()");
521
522         this.mOcResourceInfoList.clear();
523     }
524 }