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