2 // Copyright 2014 Intel Corporation.
4 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
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
10 // http://www.apache.org/licenses/LICENSE-2.0
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.
18 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
20 package org.iotivity.guiclient;
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;
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;
37 import java.util.ArrayList;
38 import java.util.EnumSet;
39 import java.util.List;
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;
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().
59 * @see org.iotivity.guiclient.OcWorkerListener
61 * Created by nathanhs on 12/22/15.
63 public class OcWorker extends Thread
64 implements OcPlatform.OnResourceFoundListener, OcResourceInfo.OcResourceInfoListener {
66 * Hardcoded TAG... if project never uses proguard then
67 * MyOcClient.class.getName() is the better way.
69 private static final String TAG = "OcWorker";
70 private Context mContext;
72 private static final boolean LOCAL_LOGV = true; // set to false to compile out verbose logging
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.
84 * These "IN" message types are for posting API-generated request actions
85 * to our OcWorker queue.
87 public enum OCW_IN_MSG {
88 DO_TEST, // developer testing only
89 DO_DISCOVER_RESOURCES,
94 DO_STOP_OBSERVING_RESOURCE;
96 private static final OCW_IN_MSG[] values = values();
98 public static OCW_IN_MSG fromInt(int i) {
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
108 private enum OC_EVENT {
110 OIC_RESOURCE_CHANGED;
112 private static final OC_EVENT[] values = OC_EVENT.values();
114 public static OC_EVENT fromInt(int i) {
119 private Handler mDoMsgHandler;
121 private Handler mOcEventHandler;
124 * The OcResourceInfo List
126 private ArrayList<OcResourceInfo> mOcResourceInfoList;
129 * The types of OIC Resources included in "FindResource" calls by this object.
131 private final String[] mOcFindQueries = {
132 COAP_CORE + RESOURCE_TYPE_QUERY + CORE_LIGHT,
133 COAP_CORE + INTERFACE_QUERY + CORE_EDISON_RESOURCES
136 private List<OcWorkerListener> mListeners;
138 public OcWorker(Context context) {
139 if (LOCAL_LOGV) Log.v(TAG, "OcWorker() constructor");
142 this.mListeners = new ArrayList<>();
146 * Set up our Handler and Looper, then initialize the OIC platform and
147 * start processing messages as they arrive.
150 if (LOCAL_LOGV) Log.v(TAG, "run()");
153 this.initHandlers(); // set up our message handler
154 this.ocInit(); // init the OIC layer including calling ConfigurePlatform
159 * Registers a listener for OcWorker events.
161 * @see org.iotivity.guiclient.OcWorkerListener
163 public void registerListener(OcWorkerListener listener) {
164 if (LOCAL_LOGV) Log.v(TAG, "registerListener()");
166 if(null != this.mListeners) {
167 this.mListeners.add(listener);
169 Log.e(TAG, "registerListener(): null mListeners list; not adding listener!");
170 Log.e(TAG, "OcWorker.run() must be called before using public methods.");
175 * The Resource discovery external API
177 public void doDiscoverResources() {
178 if (LOCAL_LOGV) Log.v(TAG, "doDiscoverResources()");
180 if(null != this.mDoMsgHandler) {
181 this.mDoMsgHandler.obtainMessage(
182 DO_DISCOVER_RESOURCES.ordinal()).sendToTarget();
184 Log.e(TAG, "doDiscoverResources(): null mDoMsgHandler; not discovering resources!");
185 Log.e(TAG, "OcWorker.run() must be called before using public methods.");
190 * The GetResource external API
192 public void doGetResource(OcResourceInfo resourceInfo) {
193 if (LOCAL_LOGV) Log.v(TAG, "doGetResource()");
195 if(null != this.mDoMsgHandler) {
196 this.mDoMsgHandler.obtainMessage(
197 DO_GET_RESOURCE.ordinal(), resourceInfo).sendToTarget();
199 Log.e(TAG, "doPutResource(): null mDoMsgHandler; not putting resource!");
200 Log.e(TAG, "OcWorker.run() must be called before using public methods.");
205 * The PutResource external API
207 public void doPutResource(OcResourceInfo resourceInfo) {
208 if (LOCAL_LOGV) Log.v(TAG, "doPutResource()");
210 if(null != this.mDoMsgHandler) {
211 this.mDoMsgHandler.obtainMessage(
212 DO_PUT_RESOURCE.ordinal(), resourceInfo).sendToTarget();
214 Log.e(TAG, "doPutResource(): null mDoMsgHandler; not putting resource!");
215 Log.e(TAG, "OcWorker.run() must be called before using public methods.");
220 * The Clear Resources external API
222 public void doClearResources() {
223 if (LOCAL_LOGV) Log.v(TAG, "doClearResources()");
225 if(null != this.mDoMsgHandler) {
226 this.mDoMsgHandler.obtainMessage(
227 DO_CLEAR_RESOURCES.ordinal()).sendToTarget();
229 Log.e(TAG, "doClearResources(): null mDoMsgHandler; not clearing resources!");
230 Log.e(TAG, "OcWorker.run() must be called before using public methods.");
237 private void initHandlers() {
238 if (LOCAL_LOGV) Log.v(TAG, "initHandler()");
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);
248 case DO_DISCOVER_RESOURCES:
251 case DO_CLEAR_RESOURCES:
252 clearResourceInfoList();
254 case DO_GET_RESOURCE:
255 getResourceAttributes((OcResourceInfo)msg.obj);
257 case DO_PUT_RESOURCE:
258 putResourceAttributes((OcResourceInfo)msg.obj);
260 case DO_OBSERVE_RESOURCE:
262 case DO_STOP_OBSERVING_RESOURCE:
265 Log.e(TAG, "unknown msg.what in handler");
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);
277 case OIC_RESOURCE_FOUND:
278 handleNewResourceFound((OcResource)msg.obj);
280 case OIC_RESOURCE_CHANGED:
281 handleResourceInfoChange((OcResourceInfo)msg.obj);
289 * Get the attributes on resourceInfo.
291 * @param resourceInfo
293 private void getResourceAttributes(OcResourceInfo resourceInfo) {
294 if (LOCAL_LOGV) Log.v(TAG, "getResourceAttributes()");
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();
302 Log.e(TAG, "getResourceAttributes(): could not find target resource.");
304 // Done. Later, the onGet listener in the OcResourceInfo object will notify us of a change
305 // via our onResourceChanged() method
309 * For each attribute in the resourceInfo.mAttributes, put the attribute value to the
312 * @param resourceInfo
314 private void putResourceAttributes(OcResourceInfo resourceInfo) {
315 if (LOCAL_LOGV) Log.v(TAG, "putResourceAttributes()");
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);
328 Log.e(TAG, "putResourceAttributes(): could not find target resource.");
330 // Done. later, the onPut listener in the OcResourceInfo object will notify us of a change
331 // via our onResourceChanged() method
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.
339 * Also note that this method must be thread safe because it can be called by
340 * multiple concurrent native threads.
344 private Object onResourceFoundLock = new Object(); // not strictly necessary with this impl.,
345 // but clears up Log message readability.
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());
354 this.mOcEventHandler.obtainMessage(OC_EVENT.OIC_RESOURCE_FOUND.ordinal(),
355 resource).sendToTarget();
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.
363 * @param resource the OcResource object
365 private void handleNewResourceFound(OcResource resource) {
366 if (LOCAL_LOGV) Log.v(TAG, String.format("handleNewResourceFound(%s)",
367 resource.toString()));
370 this.selectResourceInfoByHostAndUri(resource.getHost() + resource.getUri());
372 // before notifying listeners, update our own internal OcResourceInfo list
373 if(null != mOcResourceInfoList) {
374 // check for pre-existing duplicate before adding
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
383 // add the info object to our list
384 mOcResourceInfoList.add(ri);
388 for(OcWorkerListener l : this.mListeners) {
389 l.onResourceFound(ri);
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.
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.
402 * @param resourceInfo
404 private Object onResourceInfoChangedLock = new Object();
406 public void onResourceInfoChanged(OcResourceInfo resourceInfo) {
408 synchronized (onResourceInfoChangedLock) {
409 if (LOCAL_LOGV) Log.v(TAG, String.format("resourceInfoChanged(%s)",
410 resourceInfo.toString()));
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();
420 * Handle our internal event that is enqueued when a resource is found.
422 * @param resourceInfo
424 private void handleResourceInfoChange(OcResourceInfo resourceInfo) {
425 if (LOCAL_LOGV) Log.v(TAG, "handleResourceInfoChange()");
428 for(OcWorkerListener l : this.mListeners) {
429 l.onResourceChanged(resourceInfo);
434 * Complete OIC-related initialization, including configuring the platform
436 private void ocInit() {
437 if (LOCAL_LOGV) Log.v(TAG, "ocInit()");
439 // OIC initialization
440 mOcResourceInfoList = new ArrayList<>();
442 this.configurePlatform();
446 * Configures the OIC platform.
448 private void configurePlatform() {
449 if (LOCAL_LOGV) Log.v(TAG, "configurePlatform()");
451 PlatformConfig cfg = new PlatformConfig(
454 ModeType.CLIENT_SERVER,
455 "0.0.0.0", // bind to all available interfaces
457 QualityOfService.LOW);
459 Log.d(TAG, "configurePlatform(): calling OcPlatform.Configure()");
460 OcPlatform.Configure(cfg);
464 * Search mOcResourceInfo list for a resource whose Host and Uri concatenated
465 * matches the param passed, and return it.
467 * @param resourceHostAndUri
468 * @return OcResourceInfo with Host and Uri matching resourceHostAndUri, or null if
469 * no such OcResourceInfo exists in mOcResourceInfoList
471 private OcResourceInfo selectResourceInfoByHostAndUri(String resourceHostAndUri) {
472 if (LOCAL_LOGV) Log.v(TAG, "selectResourceByHostAndUri()");
474 boolean found = false;
475 OcResourceInfo retVal = null;
477 for(OcResourceInfo ri : mOcResourceInfoList) {
479 String s = ri.getHost() + ri.getUri();
480 if (resourceHostAndUri.equalsIgnoreCase(s)) {
487 Log.v(TAG, "selectResourceByHostAndUri(): no resource found matching HostAndUri "
488 + resourceHostAndUri);
495 * Finds OIC Resources matching known patterns.
497 * @see org.iotivity.guiclient.OcProtocolStrings
499 private void discoverResources() {
500 if (LOCAL_LOGV) Log.v(TAG, "discoverResources()");
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),
510 } catch (OcException e) {
512 Log.e(TAG, e.getMessage());
517 * Clear the ResourceInfoList
519 private void clearResourceInfoList() {
520 if (LOCAL_LOGV) Log.v(TAG, "clearResourceInfoList()");
522 this.mOcResourceInfoList.clear();