1 package io.branch.rnbranch; 2 3 import android.app.Activity; 4 import android.content.Context; 5 import android.content.Intent; 6 import android.content.IntentFilter; 7 import android.content.BroadcastReceiver; 8 import android.net.Uri; 9 import androidx.annotation.Nullable; 10 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 11 import android.util.Log; 12 import android.os.Handler; 13 14 import com.facebook.react.bridge.*; 15 import com.facebook.react.bridge.Promise; 16 import com.facebook.react.modules.core.*; 17 import com.facebook.react.bridge.ReadableMap; 18 19 import io.branch.referral.*; 20 import io.branch.referral.Branch.BranchLinkCreateListener; 21 import io.branch.referral.BuildConfig; 22 import io.branch.referral.util.*; 23 import io.branch.referral.Branch; 24 import io.branch.indexing.*; 25 26 import org.json.*; 27 28 import java.text.ParseException; 29 import java.text.SimpleDateFormat; 30 import java.util.*; 31 32 public class RNBranchModule extends ReactContextBaseJavaModule { 33 public static final String REACT_CLASS = "RNBranch"; 34 public static final String REACT_MODULE_NAME = "RNBranch"; 35 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT = "io.branch.rnbranch.RNBranchModule.onInitSessionFinished"; 36 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_BRANCH_UNIVERSAL_OBJECT = "branch_universal_object"; 37 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_LINK_PROPERTIES = "link_properties"; 38 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS = "params"; 39 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR = "error"; 40 public static final String NATIVE_INIT_SESSION_FINISHED_EVENT_URI = "uri"; 41 private static final String RN_INIT_SESSION_SUCCESS_EVENT = "RNBranch.initSessionSuccess"; 42 private static final String RN_INIT_SESSION_ERROR_EVENT = "RNBranch.initSessionError"; 43 private static final String INIT_SESSION_SUCCESS = "INIT_SESSION_SUCCESS"; 44 private static final String INIT_SESSION_ERROR = "INIT_SESSION_ERROR"; 45 private static final String ADD_TO_CART_EVENT = "ADD_TO_CART_EVENT"; 46 private static final String ADD_TO_WISHLIST_EVENT = "ADD_TO_WISHLIST_EVENT"; 47 private static final String PURCHASED_EVENT = "PURCHASED_EVENT"; 48 private static final String PURCHASE_INITIATED_EVENT = "PURCHASE_INITIATED_EVENT"; 49 private static final String REGISTER_VIEW_EVENT = "REGISTER_VIEW_EVENT"; 50 private static final String SHARE_COMPLETED_EVENT = "SHARE_COMPLETED_EVENT"; 51 private static final String SHARE_INITIATED_EVENT = "SHARE_INITIATED_EVENT"; 52 53 private static final String STANDARD_EVENT_ADD_TO_CART = "STANDARD_EVENT_ADD_TO_CART"; 54 private static final String STANDARD_EVENT_ADD_TO_WISHLIST = "STANDARD_EVENT_ADD_TO_WISHLIST"; 55 private static final String STANDARD_EVENT_VIEW_CART = "STANDARD_EVENT_VIEW_CART"; 56 private static final String STANDARD_EVENT_INITIATE_PURCHASE = "STANDARD_EVENT_INITIATE_PURCHASE"; 57 private static final String STANDARD_EVENT_ADD_PAYMENT_INFO = "STANDARD_EVENT_ADD_PAYMENT_INFO"; 58 private static final String STANDARD_EVENT_PURCHASE = "STANDARD_EVENT_PURCHASE"; 59 private static final String STANDARD_EVENT_SPEND_CREDITS = "STANDARD_EVENT_SPEND_CREDITS"; 60 61 private static final String STANDARD_EVENT_SEARCH = "STANDARD_EVENT_SEARCH"; 62 private static final String STANDARD_EVENT_VIEW_ITEM = "STANDARD_EVENT_VIEW_ITEM"; 63 private static final String STANDARD_EVENT_VIEW_ITEMS = "STANDARD_EVENT_VIEW_ITEMS"; 64 private static final String STANDARD_EVENT_RATE = "STANDARD_EVENT_RATE"; 65 private static final String STANDARD_EVENT_SHARE = "STANDARD_EVENT_SHARE"; 66 67 private static final String STANDARD_EVENT_COMPLETE_REGISTRATION = "STANDARD_EVENT_COMPLETE_REGISTRATION"; 68 private static final String STANDARD_EVENT_COMPLETE_TUTORIAL = "STANDARD_EVENT_COMPLETE_TUTORIAL"; 69 private static final String STANDARD_EVENT_ACHIEVE_LEVEL = "STANDARD_EVENT_ACHIEVE_LEVEL"; 70 private static final String STANDARD_EVENT_UNLOCK_ACHIEVEMENT = "STANDARD_EVENT_UNLOCK_ACHIEVEMENT"; 71 72 private static final String IDENT_FIELD_NAME = "ident"; 73 public static final String UNIVERSAL_OBJECT_NOT_FOUND_ERROR_CODE = "RNBranch::Error::BUONotFound"; 74 public static final String GENERIC_ERROR = "RNBranch::Error"; 75 private static final long AGING_HASH_TTL = 3600000; 76 77 private static JSONObject initSessionResult = null; 78 private BroadcastReceiver mInitSessionEventReceiver = null; 79 private static Branch.BranchUniversalReferralInitListener initListener = null; 80 81 private static Activity mActivity = null; 82 private static boolean mUseDebug = false; 83 private static boolean mInitialized = false; 84 private static JSONObject mRequestMetadata = new JSONObject(); 85 86 private AgingHash<String, BranchUniversalObject> mUniversalObjectMap = new AgingHash<>(AGING_HASH_TTL); 87 88 private static Branch.BranchReferralInitListener referralInitListener = null; 89 90 public static void getAutoInstance(Context context) { 91 RNBranchConfig config = new RNBranchConfig(context); 92 String branchKey = config.getBranchKey(); 93 String liveKey = config.getLiveKey(); 94 String testKey = config.getTestKey(); 95 boolean useTest = config.getUseTestInstance(); 96 97 if (branchKey != null) { 98 Branch.getAutoInstance(context, branchKey); 99 } 100 else if (useTest && testKey != null) { 101 Branch.getAutoInstance(context, testKey); 102 } 103 else if (!useTest && liveKey != null) { 104 Branch.getAutoInstance(context, liveKey); 105 } 106 else { 107 Branch.getAutoInstance(context); 108 } 109 } 110 111 public static void reInitSession(Activity reactActivity) { 112 Branch branch = Branch.getInstance(); 113 branch.reInitSession(reactActivity, referralInitListener); 114 } 115 116 public static void initSession(final Uri uri, Activity reactActivity, Branch.BranchUniversalReferralInitListener anInitListener) { 117 initListener = anInitListener; 118 initSession(uri, reactActivity); 119 } 120 121 public static void initSession(final Uri uri, Activity reactActivity) { 122 Branch branch = setupBranch(reactActivity.getApplicationContext()); 123 124 mActivity = reactActivity; 125 referralInitListener = new Branch.BranchReferralInitListener(){ 126 127 private Activity mmActivity = null; 128 129 @Override 130 public void onInitFinished(JSONObject referringParams, BranchError error) { 131 132 Log.d(REACT_CLASS, "onInitFinished"); 133 JSONObject result = new JSONObject(); 134 Uri referringUri = null; 135 try{ 136 boolean clickedBranchLink = false; 137 // getXXX throws. It's OK for these to be missing. 138 try { 139 clickedBranchLink = referringParams.getBoolean("+clicked_branch_link"); 140 } 141 catch (JSONException e) { 142 143 } 144 145 String referringLink = null; 146 if (clickedBranchLink) { 147 try { 148 referringLink = referringParams.getString("~referring_link"); 149 } 150 catch (JSONException e) { 151 152 } 153 } 154 else { 155 try { 156 referringLink = referringParams.getString("+non_branch_link"); 157 } 158 catch (JSONException e) { 159 160 } 161 } 162 163 if (referringLink != null) referringUri = Uri.parse(referringLink); 164 165 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS, referringParams); 166 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR, error != null ? error.getMessage() : JSONObject.NULL); 167 result.put(NATIVE_INIT_SESSION_FINISHED_EVENT_URI, referringLink != null ? referringLink : JSONObject.NULL); 168 } catch(JSONException ex) { 169 try { 170 result.put("error", "Failed to convert result to JSONObject: " + ex.getMessage()); 171 } catch(JSONException k) {} 172 } 173 initSessionResult = result; 174 175 BranchUniversalObject branchUniversalObject = BranchUniversalObject.getReferredBranchUniversalObject(); 176 LinkProperties linkProperties = LinkProperties.getReferredLinkProperties(); 177 178 if (initListener != null) { 179 initListener.onInitFinished(branchUniversalObject, linkProperties, error); 180 } 181 generateLocalBroadcast(referringParams, referringUri, branchUniversalObject, linkProperties, error); 182 } 183 184 private Branch.BranchReferralInitListener init(Activity activity) { 185 mmActivity = activity; 186 return this; 187 } 188 189 private void generateLocalBroadcast(JSONObject referringParams, 190 Uri uri, 191 BranchUniversalObject branchUniversalObject, 192 LinkProperties linkProperties, 193 BranchError error) { 194 Intent broadcastIntent = new Intent(NATIVE_INIT_SESSION_FINISHED_EVENT); 195 196 if (referringParams != null) { 197 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_PARAMS, referringParams.toString()); 198 } 199 200 if (branchUniversalObject != null) { 201 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_BRANCH_UNIVERSAL_OBJECT, branchUniversalObject); 202 } 203 204 if (linkProperties != null) { 205 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_LINK_PROPERTIES, linkProperties); 206 } 207 208 if (uri != null) { 209 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_URI, uri.toString()); 210 } 211 212 if (error != null) { 213 broadcastIntent.putExtra(NATIVE_INIT_SESSION_FINISHED_EVENT_ERROR, error.getMessage()); 214 } 215 216 LocalBroadcastManager.getInstance(mmActivity).sendBroadcast(broadcastIntent); 217 } 218 }.init(reactActivity); 219 220 branch.initSession(referralInitListener, uri, reactActivity); 221 } 222 223 public static void setDebug() { 224 mUseDebug = true; 225 } 226 227 public static void setRequestMetadata(String key, String val) { 228 if (key == null) { 229 return; 230 } 231 232 if (mRequestMetadata.has(key) && val == null) { 233 mRequestMetadata.remove(key); 234 } 235 236 try { 237 mRequestMetadata.put(key, val); 238 } catch (JSONException e) { 239 // no-op 240 } 241 } 242 243 public RNBranchModule(ReactApplicationContext reactContext) { 244 super(reactContext); 245 forwardInitSessionFinishedEventToReactNative(reactContext); 246 } 247 248 @javax.annotation.Nullable 249 @Override 250 public Map<String, Object> getConstants() { 251 final Map<String, Object> constants = new HashMap<>(); 252 // RN events transmitted to JS 253 254 constants.put(INIT_SESSION_SUCCESS, RN_INIT_SESSION_SUCCESS_EVENT); 255 constants.put(INIT_SESSION_ERROR, RN_INIT_SESSION_ERROR_EVENT); 256 257 // Constants for use with userCompletedAction (deprecated) 258 259 // constants.put(ADD_TO_CART_EVENT, BranchEvent.ADD_TO_CART); 260 // constants.put(ADD_TO_WISHLIST_EVENT, BranchEvent.ADD_TO_WISH_LIST); 261 // constants.put(PURCHASED_EVENT, BranchEvent.PURCHASED); 262 // constants.put(PURCHASE_INITIATED_EVENT, BranchEvent.PURCHASE_STARTED); 263 // constants.put(REGISTER_VIEW_EVENT, BranchEvent.VIEW); 264 // constants.put(SHARE_COMPLETED_EVENT, BranchEvent.SHARE_COMPLETED); 265 // constants.put(SHARE_INITIATED_EVENT, BranchEvent.SHARE_STARTED); 266 267 // constants for use with BranchEvent 268 269 // Commerce events 270 271 constants.put(STANDARD_EVENT_ADD_TO_CART, BRANCH_STANDARD_EVENT.ADD_TO_CART.getName()); 272 constants.put(STANDARD_EVENT_ADD_TO_WISHLIST, BRANCH_STANDARD_EVENT.ADD_TO_WISHLIST.getName()); 273 constants.put(STANDARD_EVENT_VIEW_CART, BRANCH_STANDARD_EVENT.VIEW_CART.getName()); 274 constants.put(STANDARD_EVENT_INITIATE_PURCHASE, BRANCH_STANDARD_EVENT.INITIATE_PURCHASE.getName()); 275 constants.put(STANDARD_EVENT_ADD_PAYMENT_INFO, BRANCH_STANDARD_EVENT.ADD_PAYMENT_INFO.getName()); 276 constants.put(STANDARD_EVENT_PURCHASE, BRANCH_STANDARD_EVENT.PURCHASE.getName()); 277 constants.put(STANDARD_EVENT_SPEND_CREDITS, BRANCH_STANDARD_EVENT.SPEND_CREDITS.getName()); 278 279 // Content Events 280 281 constants.put(STANDARD_EVENT_SEARCH, BRANCH_STANDARD_EVENT.SEARCH.getName()); 282 constants.put(STANDARD_EVENT_VIEW_ITEM, BRANCH_STANDARD_EVENT.VIEW_ITEM.getName()); 283 constants.put(STANDARD_EVENT_VIEW_ITEMS , BRANCH_STANDARD_EVENT.VIEW_ITEMS.getName()); 284 constants.put(STANDARD_EVENT_RATE, BRANCH_STANDARD_EVENT.RATE.getName()); 285 constants.put(STANDARD_EVENT_SHARE, BRANCH_STANDARD_EVENT.SHARE.getName()); 286 287 // User Lifecycle Events 288 289 constants.put(STANDARD_EVENT_COMPLETE_REGISTRATION, BRANCH_STANDARD_EVENT.COMPLETE_REGISTRATION.getName()); 290 constants.put(STANDARD_EVENT_COMPLETE_TUTORIAL , BRANCH_STANDARD_EVENT.COMPLETE_TUTORIAL.getName()); 291 constants.put(STANDARD_EVENT_ACHIEVE_LEVEL, BRANCH_STANDARD_EVENT.ACHIEVE_LEVEL.getName()); 292 constants.put(STANDARD_EVENT_UNLOCK_ACHIEVEMENT, BRANCH_STANDARD_EVENT.UNLOCK_ACHIEVEMENT.getName()); 293 294 return constants; 295 } 296 297 private void forwardInitSessionFinishedEventToReactNative(ReactApplicationContext reactContext) { 298 mInitSessionEventReceiver = new BroadcastReceiver() { 299 RNBranchModule mBranchModule; 300 301 @Override 302 public void onReceive(Context context, Intent intent) { 303 final boolean hasError = (initSessionResult.has("error") && !initSessionResult.isNull("error")); 304 final String eventName = hasError ? RN_INIT_SESSION_ERROR_EVENT : RN_INIT_SESSION_SUCCESS_EVENT; 305 mBranchModule.sendRNEvent(eventName, convertJsonToMap(initSessionResult)); 306 } 307 308 private BroadcastReceiver init(RNBranchModule branchModule) { 309 mBranchModule = branchModule; 310 return this; 311 } 312 }.init(this); 313 314 LocalBroadcastManager.getInstance(reactContext).registerReceiver(mInitSessionEventReceiver, new IntentFilter(NATIVE_INIT_SESSION_FINISHED_EVENT)); 315 } 316 317 @Override 318 public void onCatalystInstanceDestroy() { 319 LocalBroadcastManager.getInstance(getReactApplicationContext()).unregisterReceiver(mInitSessionEventReceiver); 320 } 321 322 @Override 323 public String getName() { 324 return REACT_MODULE_NAME; 325 } 326 327 @ReactMethod 328 public void disableTracking(boolean disable) { 329 Branch branch = Branch.getInstance(); 330 branch.disableTracking(disable); 331 } 332 333 @ReactMethod 334 public void isTrackingDisabled(Promise promise) { 335 Branch branch = Branch.getInstance(); 336 promise.resolve(branch.isTrackingDisabled()); 337 } 338 339 @ReactMethod 340 public void createUniversalObject(ReadableMap universalObjectMap, Promise promise) { 341 String ident = UUID.randomUUID().toString(); 342 BranchUniversalObject universalObject = createBranchUniversalObject(universalObjectMap); 343 mUniversalObjectMap.put(ident, universalObject); 344 345 WritableMap response = new WritableNativeMap(); 346 response.putString(IDENT_FIELD_NAME, ident); 347 promise.resolve(response); 348 } 349 350 @ReactMethod 351 public void releaseUniversalObject(String ident) { 352 mUniversalObjectMap.remove(ident); 353 } 354 355 @ReactMethod 356 public void redeemInitSessionResult(Promise promise) { 357 promise.resolve(convertJsonToMap(initSessionResult)); 358 } 359 360 @ReactMethod 361 public void getLatestReferringParams(boolean synchronous, Promise promise) { 362 Branch branch = Branch.getInstance(); 363 if (synchronous) 364 promise.resolve(convertJsonToMap(branch.getLatestReferringParamsSync())); 365 else 366 promise.resolve(convertJsonToMap(branch.getLatestReferringParams())); 367 } 368 369 @ReactMethod 370 public void getFirstReferringParams(Promise promise) { 371 Branch branch = Branch.getInstance(); 372 promise.resolve(convertJsonToMap(branch.getFirstReferringParams())); 373 } 374 375 @ReactMethod 376 public void setIdentity(String identity) { 377 Branch branch = Branch.getInstance(); 378 branch.setIdentity(identity); 379 } 380 381 @ReactMethod 382 public void logout() { 383 Branch branch = Branch.getInstance(); 384 branch.logout(); 385 } 386 387 @ReactMethod 388 public void logEvent(ReadableArray contentItems, String eventName, ReadableMap params, Promise promise) { 389 List<BranchUniversalObject> buos = new ArrayList<>(); 390 for (int i = 0; i < contentItems.size(); ++ i) { 391 String ident = contentItems.getString(i); 392 BranchUniversalObject universalObject = findUniversalObjectOrReject(ident, promise); 393 if (universalObject == null) return; 394 buos.add(universalObject); 395 } 396 397 BranchEvent event = createBranchEvent(eventName, params); 398 event.addContentItems(buos); 399 event.logEvent(mActivity); 400 promise.resolve(null); 401 } 402 403 @ReactMethod 404 public void userCompletedAction(String event, ReadableMap appState) throws JSONException { 405 Branch branch = Branch.getInstance(); 406 branch.userCompletedAction(event, convertMapToJson(appState)); 407 } 408 409 @ReactMethod 410 public void userCompletedActionOnUniversalObject(String ident, String event, ReadableMap state, Promise promise) { 411 BranchUniversalObject universalObject = findUniversalObjectOrReject(ident, promise); 412 if (universalObject == null) return; 413 414 universalObject.userCompletedAction(event, convertMapToParams(state)); 415 promise.resolve(null); 416 } 417 418 @ReactMethod 419 public void sendCommerceEvent(String revenue, ReadableMap metadata, final Promise promise) throws JSONException { 420 Branch branch = Branch.getInstance(); 421 422 CommerceEvent commerceEvent = new CommerceEvent(); 423 commerceEvent.setRevenue(Double.parseDouble(revenue)); 424 425 JSONObject jsonMetadata = null; 426 if (metadata != null) { 427 jsonMetadata = convertMapToJson(metadata); 428 } 429 430 branch.sendCommerceEvent(commerceEvent, jsonMetadata, null); 431 promise.resolve(null); 432 } 433 434 @ReactMethod 435 public void showShareSheet(String ident, ReadableMap shareOptionsMap, ReadableMap linkPropertiesMap, ReadableMap controlParamsMap, Promise promise) { 436 Context context = getReactApplicationContext(); 437 438 Handler mainHandler = new Handler(context.getMainLooper()); 439 440 Runnable myRunnable = new Runnable() { 441 Promise mPm; 442 Context mContext; 443 ReadableMap shareOptionsMap, linkPropertiesMap, controlParamsMap; 444 String ident; 445 446 private Runnable init(ReadableMap _shareOptionsMap, String _ident, ReadableMap _linkPropertiesMap, ReadableMap _controlParamsMap, Promise promise, Context context) { 447 mPm = promise; 448 mContext = context; 449 shareOptionsMap = _shareOptionsMap; 450 ident = _ident; 451 linkPropertiesMap = _linkPropertiesMap; 452 controlParamsMap = _controlParamsMap; 453 return this; 454 } 455 456 @Override 457 public void run() { 458 String messageHeader = shareOptionsMap.hasKey("messageHeader") ? shareOptionsMap.getString("messageHeader") : ""; 459 String messageBody = shareOptionsMap.hasKey("messageBody") ? shareOptionsMap.getString("messageBody") : ""; 460 ShareSheetStyle shareSheetStyle = new ShareSheetStyle(mContext, messageHeader, messageBody) 461 .setCopyUrlStyle(mContext.getResources().getDrawable(android.R.drawable.ic_menu_send), "Copy", "Added to clipboard") 462 .setMoreOptionStyle(mContext.getResources().getDrawable(android.R.drawable.ic_menu_search), "Show more") 463 .addPreferredSharingOption(SharingHelper.SHARE_WITH.EMAIL) 464 .addPreferredSharingOption(SharingHelper.SHARE_WITH.TWITTER) 465 .addPreferredSharingOption(SharingHelper.SHARE_WITH.MESSAGE) 466 .addPreferredSharingOption(SharingHelper.SHARE_WITH.FACEBOOK); 467 468 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, mPm); 469 if (branchUniversalObject == null) { 470 return; 471 } 472 473 LinkProperties linkProperties = createLinkProperties(linkPropertiesMap, controlParamsMap); 474 475 branchUniversalObject.showShareSheet( 476 getCurrentActivity(), 477 linkProperties, 478 shareSheetStyle, 479 new Branch.BranchLinkShareListener() { 480 private Promise mPromise = null; 481 482 @Override 483 public void onShareLinkDialogLaunched() { 484 } 485 486 @Override 487 public void onShareLinkDialogDismissed() { 488 if(mPromise == null) { 489 return; 490 } 491 492 WritableMap map = new WritableNativeMap(); 493 map.putString("channel", null); 494 map.putBoolean("completed", false); 495 map.putString("error", null); 496 mPromise.resolve(map); 497 mPromise = null; 498 } 499 500 @Override 501 public void onLinkShareResponse(String sharedLink, String sharedChannel, BranchError error) { 502 if(mPromise == null) { 503 return; 504 } 505 506 WritableMap map = new WritableNativeMap(); 507 map.putString("channel", sharedChannel); 508 map.putBoolean("completed", true); 509 map.putString("error", (error != null ? error.getMessage() : null)); 510 mPromise.resolve(map); 511 mPromise = null; 512 } 513 @Override 514 public void onChannelSelected(String channelName) { 515 } 516 517 private Branch.BranchLinkShareListener init(Promise promise) { 518 mPromise = promise; 519 return this; 520 } 521 }.init(mPm)); 522 } 523 }.init(shareOptionsMap, ident, linkPropertiesMap, controlParamsMap, promise, context); 524 525 mainHandler.post(myRunnable); 526 } 527 528 @ReactMethod 529 public void registerView(String ident, Promise promise) { 530 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, promise); 531 if (branchUniversalObject == null) { 532 return; 533 } 534 535 branchUniversalObject.registerView(); 536 promise.resolve(null); 537 } 538 539 @ReactMethod 540 public void generateShortUrl(String ident, ReadableMap linkPropertiesMap, ReadableMap controlParamsMap, final Promise promise) { 541 LinkProperties linkProperties = createLinkProperties(linkPropertiesMap, controlParamsMap); 542 543 BranchUniversalObject branchUniversalObject = findUniversalObjectOrReject(ident, promise); 544 if (branchUniversalObject == null) { 545 return; 546 } 547 548 branchUniversalObject.generateShortUrl(mActivity, linkProperties, new BranchLinkCreateListener() { 549 @Override 550 public void onLinkCreate(String url, BranchError error) { 551 Log.d(REACT_CLASS, "onLinkCreate " + url); 552 if (error != null) { 553 if (error.getErrorCode() == BranchError.ERR_BRANCH_DUPLICATE_URL) { 554 promise.reject("RNBranch::Error::DuplicateResourceError", error.getMessage()); 555 } 556 else { 557 promise.reject(GENERIC_ERROR, error.getMessage()); 558 } 559 return; 560 } 561 562 WritableMap map = new WritableNativeMap(); 563 map.putString("url", url); 564 promise.resolve(map); 565 } 566 }); 567 } 568 569 @ReactMethod 570 public void openURL(String url, ReadableMap options) { 571 if (mActivity == null) { 572 // initSession is called before JS loads. This probably indicates failure to call initSession 573 // in an activity. 574 Log.e(REACT_CLASS, "Branch native Android SDK not initialized in openURL"); 575 return; 576 } 577 578 Intent intent = new Intent(mActivity, mActivity.getClass()); 579 intent.putExtra("branch", url); 580 intent.putExtra("branch_force_new_session", true); 581 582 if (options.hasKey("newActivity") && options.getBoolean("newActivity")) mActivity.finish(); 583 mActivity.startActivity(intent); 584 } 585 586 public static BranchEvent createBranchEvent(String eventName, ReadableMap params) { 587 BranchEvent event; 588 try { 589 BRANCH_STANDARD_EVENT standardEvent = BRANCH_STANDARD_EVENT.valueOf(eventName); 590 // valueOf on BRANCH_STANDARD_EVENT Enum has succeeded, so this is a standard event. 591 event = new BranchEvent(standardEvent); 592 } catch (IllegalArgumentException e) { 593 // The event name is not found in standard events. 594 // So use custom event mode. 595 event = new BranchEvent(eventName); 596 } 597 598 if (params.hasKey("currency")) { 599 String currencyString = params.getString("currency"); 600 CurrencyType currency = CurrencyType.getValue(currencyString); 601 if (currency != null) { 602 event.setCurrency(currency); 603 } 604 else { 605 Log.w(REACT_CLASS, "Invalid currency " + currencyString); 606 } 607 } 608 609 if (params.hasKey("transactionID")) event.setTransactionID(params.getString("transactionID")); 610 if (params.hasKey("revenue")) event.setRevenue(Double.parseDouble(params.getString("revenue"))); 611 if (params.hasKey("shipping")) event.setShipping(Double.parseDouble(params.getString("shipping"))); 612 if (params.hasKey("tax")) event.setTax(Double.parseDouble(params.getString("tax"))); 613 if (params.hasKey("coupon")) event.setCoupon(params.getString("coupon")); 614 if (params.hasKey("affiliation")) event.setAffiliation(params.getString("affiliation")); 615 if (params.hasKey("description")) event.setDescription(params.getString("description")); 616 if (params.hasKey("searchQuery")) event.setSearchQuery(params.getString("searchQuery")); 617 618 if (params.hasKey("customData")) { 619 ReadableMap customData = params.getMap("customData"); 620 ReadableMapKeySetIterator it = customData.keySetIterator(); 621 while (it.hasNextKey()) { 622 String key = it.nextKey(); 623 event.addCustomDataProperty(key, customData.getString(key)); 624 } 625 } 626 627 return event; 628 } 629 630 public static LinkProperties createLinkProperties(ReadableMap linkPropertiesMap, @Nullable ReadableMap controlParams){ 631 LinkProperties linkProperties = new LinkProperties(); 632 if (linkPropertiesMap.hasKey("alias")) linkProperties.setAlias(linkPropertiesMap.getString("alias")); 633 if (linkPropertiesMap.hasKey("campaign")) linkProperties.setCampaign(linkPropertiesMap.getString("campaign")); 634 if (linkPropertiesMap.hasKey("channel")) linkProperties.setChannel(linkPropertiesMap.getString("channel")); 635 if (linkPropertiesMap.hasKey("feature")) linkProperties.setFeature(linkPropertiesMap.getString("feature")); 636 if (linkPropertiesMap.hasKey("stage")) linkProperties.setStage(linkPropertiesMap.getString("stage")); 637 638 if (linkPropertiesMap.hasKey("tags")) { 639 ReadableArray tags = linkPropertiesMap.getArray("tags"); 640 for (int i=0; i<tags.size(); ++i) { 641 String tag = tags.getString(i); 642 linkProperties.addTag(tag); 643 } 644 } 645 646 if (controlParams != null) { 647 ReadableMapKeySetIterator iterator = controlParams.keySetIterator(); 648 while (iterator.hasNextKey()) { 649 String key = iterator.nextKey(); 650 Object value = getReadableMapObjectForKey(controlParams, key); 651 linkProperties.addControlParameter(key, value.toString()); 652 } 653 } 654 655 return linkProperties; 656 } 657 658 private static Branch setupBranch(Context context) { 659 Branch branch = Branch.getInstance(context); 660 661 if (!mInitialized) { 662 Log.i(REACT_CLASS, "Initializing Branch SDK v. " + BuildConfig.VERSION_NAME); 663 664 RNBranchConfig config = new RNBranchConfig(context); 665 666 if (mUseDebug || config.getDebugMode()) branch.setDebug(); 667 668 if (mRequestMetadata != null) { 669 Iterator keys = mRequestMetadata.keys(); 670 while (keys.hasNext()) { 671 String key = (String) keys.next(); 672 try { 673 branch.setRequestMetadata(key, mRequestMetadata.getString(key)); 674 } catch (JSONException e) { 675 // no-op 676 } 677 } 678 } 679 680 mInitialized = true; 681 } 682 683 return branch; 684 } 685 686 private BranchUniversalObject findUniversalObjectOrReject(final String ident, final Promise promise) { 687 BranchUniversalObject universalObject = mUniversalObjectMap.get(ident); 688 689 if (universalObject == null) { 690 final String errorMessage = "BranchUniversalObject not found for ident " + ident + "."; 691 promise.reject(UNIVERSAL_OBJECT_NOT_FOUND_ERROR_CODE, errorMessage); 692 } 693 694 return universalObject; 695 } 696 697 public ContentMetadata createContentMetadata(ReadableMap map) { 698 ContentMetadata metadata = new ContentMetadata(); 699 700 if (map.hasKey("contentSchema")) { 701 BranchContentSchema schema = BranchContentSchema.valueOf(map.getString("contentSchema")); 702 metadata.setContentSchema(schema); 703 } 704 705 if (map.hasKey("quantity")) { 706 metadata.setQuantity(map.getDouble("quantity")); 707 } 708 709 if (map.hasKey("price")) { 710 double price = Double.parseDouble(map.getString("price")); 711 CurrencyType currency = null; 712 if (map.hasKey("currency")) currency = CurrencyType.valueOf(map.getString("currency")); 713 metadata.setPrice(price, currency); 714 } 715 716 if (map.hasKey("sku")) { 717 metadata.setSku(map.getString("sku")); 718 } 719 720 if (map.hasKey("productName")) { 721 metadata.setProductName(map.getString("productName")); 722 } 723 724 if (map.hasKey("productBrand")) { 725 metadata.setProductBrand(map.getString("productBrand")); 726 } 727 728 if (map.hasKey("productCategory")) { 729 ProductCategory category = getProductCategory(map.getString("productCategory")); 730 if (category != null) metadata.setProductCategory(category); 731 } 732 733 if (map.hasKey("productVariant")) { 734 metadata.setProductVariant(map.getString("productVariant")); 735 } 736 737 if (map.hasKey("condition")) { 738 ContentMetadata.CONDITION condition = ContentMetadata.CONDITION.valueOf(map.getString("condition")); 739 metadata.setProductCondition(condition); 740 } 741 742 if (map.hasKey("ratingAverage") || map.hasKey("ratingMax") || map.hasKey("ratingCount")) { 743 Double average = null, max = null; 744 Integer count = null; 745 if (map.hasKey("ratingAverage")) average = map.getDouble("ratingAverage"); 746 if (map.hasKey("ratingCount")) count = map.getInt("ratingCount"); 747 if (map.hasKey("ratingMax")) max = map.getDouble("ratingMax"); 748 metadata.setRating(average, max, count); 749 } 750 751 if (map.hasKey("addressStreet") || 752 map.hasKey("addressCity") || 753 map.hasKey("addressRegion") || 754 map.hasKey("addressCountry") || 755 map.hasKey("addressPostalCode")) { 756 String street = null, city = null, region = null, country = null, postalCode = null; 757 if (map.hasKey("addressStreet")) street = map.getString("addressStreet"); 758 if (map.hasKey("addressCity")) street = map.getString("addressCity"); 759 if (map.hasKey("addressRegion")) street = map.getString("addressRegion"); 760 if (map.hasKey("addressCountry")) street = map.getString("addressCountry"); 761 if (map.hasKey("addressPostalCode")) street = map.getString("addressPostalCode"); 762 metadata.setAddress(street, city, region, country, postalCode); 763 } 764 765 if (map.hasKey("latitude") || map.hasKey("longitude")) { 766 Double latitude = null, longitude = null; 767 if (map.hasKey("latitude")) latitude = map.getDouble("latitude"); 768 if (map.hasKey("longitude")) longitude = map.getDouble("longitude"); 769 metadata.setLocation(latitude, longitude); 770 } 771 772 if (map.hasKey("imageCaptions")) { 773 ReadableArray captions = map.getArray("imageCaptions"); 774 for (int j=0; j < captions.size(); ++j) { 775 metadata.addImageCaptions(captions.getString(j)); 776 } 777 } 778 779 if (map.hasKey("customMetadata")) { 780 ReadableMap customMetadata = map.getMap("customMetadata"); 781 ReadableMapKeySetIterator it = customMetadata.keySetIterator(); 782 while (it.hasNextKey()) { 783 String key = it.nextKey(); 784 metadata.addCustomMetadata(key, customMetadata.getString(key)); 785 } 786 } 787 788 return metadata; 789 } 790 791 public BranchUniversalObject createBranchUniversalObject(ReadableMap branchUniversalObjectMap) { 792 BranchUniversalObject branchUniversalObject = new BranchUniversalObject() 793 .setCanonicalIdentifier(branchUniversalObjectMap.getString("canonicalIdentifier")); 794 795 if (branchUniversalObjectMap.hasKey("title")) branchUniversalObject.setTitle(branchUniversalObjectMap.getString("title")); 796 if (branchUniversalObjectMap.hasKey("canonicalUrl")) branchUniversalObject.setCanonicalUrl(branchUniversalObjectMap.getString("canonicalUrl")); 797 if (branchUniversalObjectMap.hasKey("contentDescription")) branchUniversalObject.setContentDescription(branchUniversalObjectMap.getString("contentDescription")); 798 if (branchUniversalObjectMap.hasKey("contentImageUrl")) branchUniversalObject.setContentImageUrl(branchUniversalObjectMap.getString("contentImageUrl")); 799 800 if (branchUniversalObjectMap.hasKey("locallyIndex")) { 801 if (branchUniversalObjectMap.getBoolean("locallyIndex")) { 802 branchUniversalObject.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 803 } 804 else { 805 branchUniversalObject.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 806 } 807 } 808 809 if (branchUniversalObjectMap.hasKey("publiclyIndex")) { 810 if (branchUniversalObjectMap.getBoolean("publiclyIndex")) { 811 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 812 } 813 else { 814 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 815 } 816 } 817 818 if (branchUniversalObjectMap.hasKey("contentIndexingMode")) { 819 switch (branchUniversalObjectMap.getType("contentIndexingMode")) { 820 case String: 821 String mode = branchUniversalObjectMap.getString("contentIndexingMode"); 822 823 if (mode.equals("private")) 824 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); 825 else if (mode.equals("public")) 826 branchUniversalObject.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); 827 else 828 Log.w(REACT_CLASS, "Unsupported value for contentIndexingMode: " + mode + 829 ". Supported values are \"public\" and \"private\""); 830 break; 831 default: 832 Log.w(REACT_CLASS, "contentIndexingMode must be a String"); 833 break; 834 } 835 } 836 837 if (branchUniversalObjectMap.hasKey("currency") && branchUniversalObjectMap.hasKey("price")) { 838 String currencyString = branchUniversalObjectMap.getString("currency"); 839 CurrencyType currency = CurrencyType.valueOf(currencyString); 840 branchUniversalObject.setPrice(branchUniversalObjectMap.getDouble("price"), currency); 841 } 842 843 if (branchUniversalObjectMap.hasKey("expirationDate")) { 844 String expirationString = branchUniversalObjectMap.getString("expirationDate"); 845 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); 846 format.setTimeZone(TimeZone.getTimeZone("UTC")); 847 try { 848 Date date = format.parse(expirationString); 849 Log.d(REACT_CLASS, "Expiration date is " + date.toString()); 850 branchUniversalObject.setContentExpiration(date); 851 } 852 catch (ParseException e) { 853 Log.w(REACT_CLASS, "Invalid expiration date format. Valid format is YYYY-mm-ddTHH:MM:SS, e.g. 2017-02-01T00:00:00. All times UTC."); 854 } 855 } 856 857 if (branchUniversalObjectMap.hasKey("keywords")) { 858 ReadableArray keywords = branchUniversalObjectMap.getArray("keywords"); 859 for (int i=0; i<keywords.size(); ++i) { 860 branchUniversalObject.addKeyWord(keywords.getString(i)); 861 } 862 } 863 864 if(branchUniversalObjectMap.hasKey("metadata")) { 865 ReadableMap metadataMap = branchUniversalObjectMap.getMap("metadata"); 866 ReadableMapKeySetIterator iterator = metadataMap.keySetIterator(); 867 while (iterator.hasNextKey()) { 868 String metadataKey = iterator.nextKey(); 869 Object metadataObject = getReadableMapObjectForKey(metadataMap, metadataKey); 870 branchUniversalObject.addContentMetadata(metadataKey, metadataObject.toString()); 871 HashMap<String, String> metadata = branchUniversalObject.getMetadata(); 872 } 873 } 874 875 if (branchUniversalObjectMap.hasKey("type")) branchUniversalObject.setContentType(branchUniversalObjectMap.getString("type")); 876 877 if (branchUniversalObjectMap.hasKey("contentMetadata")) { 878 branchUniversalObject.setContentMetadata(createContentMetadata(branchUniversalObjectMap.getMap("contentMetadata"))); 879 } 880 881 return branchUniversalObject; 882 } 883 884 @Nullable 885 public ProductCategory getProductCategory(final String stringValue) { 886 ProductCategory[] possibleValues = ProductCategory.class.getEnumConstants(); 887 for (ProductCategory value: possibleValues) { 888 if (stringValue.equals(value.getName())) { 889 return value; 890 } 891 } 892 Log.w(REACT_CLASS, "Could not find product category " + stringValue); 893 return null; 894 } 895 896 @ReactMethod 897 public void redeemRewards(int value, String bucket, Promise promise) 898 { 899 if (bucket == null) { 900 Branch.getInstance().redeemRewards(value, new RedeemRewardsListener(promise)); 901 } else { 902 Branch.getInstance().redeemRewards(bucket, value, new RedeemRewardsListener(promise)); 903 } 904 } 905 906 @ReactMethod 907 public void loadRewards(String bucket, Promise promise) 908 { 909 Branch.getInstance().loadRewards(new LoadRewardsListener(bucket, promise)); 910 } 911 912 @ReactMethod 913 public void getCreditHistory(Promise promise) 914 { 915 Branch.getInstance().getCreditHistory(new CreditHistoryListener(promise)); 916 } 917 918 protected class CreditHistoryListener implements Branch.BranchListResponseListener 919 { 920 private Promise _promise; 921 922 // Constructor that takes in a required callbackContext object 923 public CreditHistoryListener(Promise promise) { 924 this._promise = promise; 925 } 926 927 // Listener that implements BranchListResponseListener for getCreditHistory() 928 @Override 929 public void onReceivingResponse(JSONArray list, BranchError error) { 930 ArrayList<String> errors = new ArrayList<String>(); 931 if (error == null) { 932 try { 933 ReadableArray result = convertJsonToArray(list); 934 this._promise.resolve(result); 935 } catch (JSONException err) { 936 this._promise.reject(GENERIC_ERROR, err.getMessage()); 937 } 938 } else { 939 String errorMessage = error.getMessage(); 940 Log.d(REACT_CLASS, errorMessage); 941 this._promise.reject(GENERIC_ERROR, errorMessage); 942 } 943 } 944 } 945 946 protected class RedeemRewardsListener implements Branch.BranchReferralStateChangedListener 947 { 948 private Promise _promise; 949 950 public RedeemRewardsListener(Promise promise) { 951 this._promise = promise; 952 } 953 954 @Override 955 public void onStateChanged(boolean changed, BranchError error) { 956 if (error == null) { 957 WritableMap map = new WritableNativeMap(); 958 map.putBoolean("changed", changed); 959 this._promise.resolve(map); 960 } else { 961 String errorMessage = error.getMessage(); 962 Log.d(REACT_CLASS, errorMessage); 963 this._promise.reject(GENERIC_ERROR, errorMessage); 964 } 965 } 966 } 967 968 protected class LoadRewardsListener implements Branch.BranchReferralStateChangedListener 969 { 970 private String _bucket; 971 private Promise _promise; 972 973 public LoadRewardsListener(String bucket, Promise promise) { 974 this._bucket = bucket; 975 this._promise = promise; 976 } 977 978 @Override 979 public void onStateChanged(boolean changed, BranchError error) { 980 if (error == null) { 981 int credits = 0; 982 if (this._bucket == null) { 983 credits = Branch.getInstance().getCredits(); 984 } else { 985 credits = Branch.getInstance().getCreditsForBucket(this._bucket); 986 } 987 WritableMap map = new WritableNativeMap(); 988 map.putInt("credits", credits); 989 this._promise.resolve(map); 990 } else { 991 String errorMessage = error.getMessage(); 992 Log.d(REACT_CLASS, errorMessage); 993 this._promise.reject(GENERIC_ERROR, errorMessage); 994 } 995 } 996 } 997 998 public void sendRNEvent(String eventName, @Nullable WritableMap params) { 999 // This should avoid the crash in getJSModule() at startup 1000 // See also: https://github.com/walmartreact/react-native-orientation-listener/issues/8 1001 1002 ReactApplicationContext context = getReactApplicationContext(); 1003 Handler mainHandler = new Handler(context.getMainLooper()); 1004 1005 Runnable poller = new Runnable() { 1006 1007 private Runnable init(ReactApplicationContext _context, Handler _mainHandler, String _eventName, WritableMap _params) { 1008 mMainHandler = _mainHandler; 1009 mEventName = _eventName; 1010 mContext = _context; 1011 mParams = _params; 1012 return this; 1013 } 1014 1015 final int pollDelayInMs = 100; 1016 final int maxTries = 300; 1017 1018 int tries = 1; 1019 String mEventName; 1020 WritableMap mParams; 1021 Handler mMainHandler; 1022 ReactApplicationContext mContext; 1023 1024 @Override 1025 public void run() { 1026 try { 1027 Log.d(REACT_CLASS, "Catalyst instance poller try " + Integer.toString(tries)); 1028 if (mContext.hasActiveCatalystInstance()) { 1029 Log.d(REACT_CLASS, "Catalyst instance active"); 1030 mContext 1031 .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 1032 .emit(mEventName, mParams); 1033 } else { 1034 tries++; 1035 if (tries <= maxTries) { 1036 mMainHandler.postDelayed(this, pollDelayInMs); 1037 } else { 1038 Log.e(REACT_CLASS, "Could not get Catalyst instance"); 1039 } 1040 } 1041 } 1042 catch (Exception e) { 1043 e.printStackTrace(); 1044 } 1045 } 1046 }.init(context, mainHandler, eventName, params); 1047 1048 Log.d(REACT_CLASS, "sendRNEvent"); 1049 1050 mainHandler.post(poller); 1051 } 1052 1053 private static Object getReadableMapObjectForKey(ReadableMap readableMap, String key) { 1054 switch (readableMap.getType(key)) { 1055 case Null: 1056 return "Null"; 1057 case Boolean: 1058 return readableMap.getBoolean(key); 1059 case Number: 1060 if (readableMap.getDouble(key) % 1 == 0) { 1061 return readableMap.getInt(key); 1062 } else { 1063 return readableMap.getDouble(key); 1064 } 1065 case String: 1066 return readableMap.getString(key); 1067 default: 1068 return "Unsupported Type"; 1069 } 1070 } 1071 1072 private static JSONObject convertMapToJson(ReadableMap readableMap) throws JSONException { 1073 JSONObject object = new JSONObject(); 1074 ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 1075 while (iterator.hasNextKey()) { 1076 String key = iterator.nextKey(); 1077 switch (readableMap.getType(key)) { 1078 case Null: 1079 object.put(key, JSONObject.NULL); 1080 break; 1081 case Boolean: 1082 object.put(key, readableMap.getBoolean(key)); 1083 break; 1084 case Number: 1085 object.put(key, readableMap.getDouble(key)); 1086 break; 1087 case String: 1088 object.put(key, readableMap.getString(key)); 1089 break; 1090 case Map: 1091 object.put(key, convertMapToJson(readableMap.getMap(key))); 1092 break; 1093 case Array: 1094 object.put(key, convertArrayToJson(readableMap.getArray(key))); 1095 break; 1096 } 1097 } 1098 return object; 1099 } 1100 1101 private static JSONArray convertArrayToJson(ReadableArray readableArray) throws JSONException { 1102 JSONArray array = new JSONArray(); 1103 for (int i = 0; i < readableArray.size(); i++) { 1104 switch (readableArray.getType(i)) { 1105 case Null: 1106 break; 1107 case Boolean: 1108 array.put(readableArray.getBoolean(i)); 1109 break; 1110 case Number: 1111 array.put(readableArray.getDouble(i)); 1112 break; 1113 case String: 1114 array.put(readableArray.getString(i)); 1115 break; 1116 case Map: 1117 array.put(convertMapToJson(readableArray.getMap(i))); 1118 break; 1119 case Array: 1120 array.put(convertArrayToJson(readableArray.getArray(i))); 1121 break; 1122 } 1123 } 1124 return array; 1125 } 1126 1127 private static WritableMap convertJsonToMap(JSONObject jsonObject) { 1128 if(jsonObject == null) { 1129 return null; 1130 } 1131 1132 WritableMap map = new WritableNativeMap(); 1133 1134 try { 1135 Iterator<String> iterator = jsonObject.keys(); 1136 while (iterator.hasNext()) { 1137 String key = iterator.next(); 1138 Object value = jsonObject.get(key); 1139 if (value instanceof JSONObject) { 1140 map.putMap(key, convertJsonToMap((JSONObject) value)); 1141 } else if (value instanceof JSONArray) { 1142 map.putArray(key, convertJsonToArray((JSONArray) value)); 1143 } else if (value instanceof Boolean) { 1144 map.putBoolean(key, (Boolean) value); 1145 } else if (value instanceof Integer) { 1146 map.putInt(key, (Integer) value); 1147 } else if (value instanceof Double) { 1148 map.putDouble(key, (Double) value); 1149 } else if (value instanceof String) { 1150 map.putString(key, (String) value); 1151 } else if (value == null || value == JSONObject.NULL) { 1152 map.putNull(key); 1153 } else { 1154 map.putString(key, value.toString()); 1155 } 1156 } 1157 } catch(JSONException ex) { 1158 map.putString("error", "Failed to convert JSONObject to WriteableMap: " + ex.getMessage()); 1159 } 1160 1161 return map; 1162 } 1163 1164 private static WritableArray convertJsonToArray(JSONArray jsonArray) throws JSONException { 1165 WritableArray array = new WritableNativeArray(); 1166 1167 for (int i = 0; i < jsonArray.length(); i++) { 1168 Object value = jsonArray.get(i); 1169 if (value instanceof JSONObject) { 1170 array.pushMap(convertJsonToMap((JSONObject) value)); 1171 } else if (value instanceof JSONArray) { 1172 array.pushArray(convertJsonToArray((JSONArray) value)); 1173 } else if (value instanceof Boolean) { 1174 array.pushBoolean((Boolean) value); 1175 } else if (value instanceof Integer) { 1176 array.pushInt((Integer) value); 1177 } else if (value instanceof Double) { 1178 array.pushDouble((Double) value); 1179 } else if (value instanceof String) { 1180 array.pushString((String) value); 1181 } else { 1182 array.pushString(value.toString()); 1183 } 1184 } 1185 return array; 1186 } 1187 1188 // Convert an arbitrary ReadableMap to a string-string hash of custom params for userCompletedAction. 1189 private static HashMap<String, String> convertMapToParams(ReadableMap map) { 1190 if (map == null) return null; 1191 1192 HashMap<String, String> hash = new HashMap<>(); 1193 1194 ReadableMapKeySetIterator iterator = map.keySetIterator(); 1195 while (iterator.hasNextKey()) { 1196 String key = iterator.nextKey(); 1197 switch (map.getType(key)) { 1198 case String: 1199 hash.put(key, map.getString(key)); 1200 case Boolean: 1201 hash.put(key, "" + map.getBoolean(key)); 1202 case Number: 1203 hash.put(key, "" + map.getDouble(key)); 1204 default: 1205 Log.w(REACT_CLASS, "Unsupported data type in params, ignoring"); 1206 } 1207 } 1208 1209 return hash; 1210 } 1211 } 1212