@Nullable
 @Override
 public Map<String, Object> getExportedViewConstants() {
   return MapBuilder.<String, Object>of(
       "SIZE",
       MapBuilder.of("DEFAULT", SwipeRefreshLayout.DEFAULT, "LARGE", SwipeRefreshLayout.LARGE));
 }
 @Nullable
 @Override
 public Map<String, Object> getExportedViewConstants() {
   return MapBuilder.<String, Object>of(
       "ShowAsAction",
       MapBuilder.of(
           "never", MenuItem.SHOW_AS_ACTION_NEVER,
           "always", MenuItem.SHOW_AS_ACTION_ALWAYS,
           "ifRoom", MenuItem.SHOW_AS_ACTION_IF_ROOM));
 }
 /* package */ static Map getDirectEventTypeConstants() {
   return MapBuilder.builder()
       .put("topContentSizeChange", MapBuilder.of("registrationName", "onContentSizeChange"))
       .put("topLayout", MapBuilder.of("registrationName", "onLayout"))
       .put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError"))
       .put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish"))
       .put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart"))
       .put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange"))
       .put("topMessage", MapBuilder.of("registrationName", "onMessage"))
       .build();
 }
Exemplo n.º 4
0
 @Override
 public Map<String, Integer> getCommandsMap() {
   return MapBuilder.of("hotspotUpdate", CMD_HOTSPOT_UPDATE, "setPressed", CMD_SET_PRESSED);
 }
  public static Map<String, Object> getConstants() {
    HashMap<String, Object> constants = new HashMap<String, Object>();
    constants.put(
        "UIView",
        MapBuilder.of(
            "ContentMode",
            MapBuilder.of(
                "ScaleAspectFit",
                ImageView.ScaleType.FIT_CENTER.ordinal(),
                "ScaleAspectFill",
                ImageView.ScaleType.CENTER_CROP.ordinal(),
                "ScaleAspectCenter",
                ImageView.ScaleType.CENTER_INSIDE.ordinal())));

    DisplayMetrics displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics();
    DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics();
    constants.put(
        "Dimensions",
        MapBuilder.of(
            "windowPhysicalPixels",
            MapBuilder.of(
                "width",
                displayMetrics.widthPixels,
                "height",
                displayMetrics.heightPixels,
                "scale",
                displayMetrics.density,
                "fontScale",
                displayMetrics.scaledDensity,
                "densityDpi",
                displayMetrics.densityDpi),
            "screenPhysicalPixels",
            MapBuilder.of(
                "width",
                screenDisplayMetrics.widthPixels,
                "height",
                screenDisplayMetrics.heightPixels,
                "scale",
                screenDisplayMetrics.density,
                "fontScale",
                screenDisplayMetrics.scaledDensity,
                "densityDpi",
                screenDisplayMetrics.densityDpi)));

    constants.put(
        "StyleConstants",
        MapBuilder.of(
            "PointerEventsValues",
            MapBuilder.of(
                "none",
                PointerEvents.NONE.ordinal(),
                "boxNone",
                PointerEvents.BOX_NONE.ordinal(),
                "boxOnly",
                PointerEvents.BOX_ONLY.ordinal(),
                "unspecified",
                PointerEvents.AUTO.ordinal())));

    constants.put(
        "PopupMenu",
        MapBuilder.of(
            ACTION_DISMISSED, ACTION_DISMISSED, ACTION_ITEM_SELECTED, ACTION_ITEM_SELECTED));

    constants.put(
        "AccessibilityEventTypes",
        MapBuilder.of(
            "typeWindowStateChanged",
            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
            "typeViewClicked",
            AccessibilityEvent.TYPE_VIEW_CLICKED));

    return constants;
  }
 /* package */ static Map getBubblingEventTypeConstants() {
   return MapBuilder.builder()
       .put(
           "topChange",
           MapBuilder.of(
               "phasedRegistrationNames",
               MapBuilder.of("bubbled", "onChange", "captured", "onChangeCapture")))
       .put(
           "topSelect",
           MapBuilder.of(
               "phasedRegistrationNames",
               MapBuilder.of("bubbled", "onSelect", "captured", "onSelectCapture")))
       .put(
           TouchEventType.START.getJSEventName(),
           MapBuilder.of(
               "phasedRegistrationNames",
               MapBuilder.of("bubbled", "onTouchStart", "captured", "onTouchStartCapture")))
       .put(
           TouchEventType.MOVE.getJSEventName(),
           MapBuilder.of(
               "phasedRegistrationNames",
               MapBuilder.of("bubbled", "onTouchMove", "captured", "onTouchMoveCapture")))
       .put(
           TouchEventType.END.getJSEventName(),
           MapBuilder.of(
               "phasedRegistrationNames",
               MapBuilder.of("bubbled", "onTouchEnd", "captured", "onTouchEndCapture")))
       .build();
 }
/**
 * Class responsible for dispatching UI events to JS. The main purpose of this class is to act as an
 * intermediary between UI code generating events and JS, making sure we don't send more events than
 * JS can process.
 *
 * <p>To use it, create a subclass of {@link Event} and call {@link #dispatchEvent(Event)} whenever
 * there's a UI event to dispatch.
 *
 * <p>This class works by installing a Choreographer frame callback on the main thread. This
 * callback then enqueues a runnable on the JS thread (if one is not already pending) that is
 * responsible for actually dispatch events to JS. This implementation depends on the properties
 * that 1) FrameCallbacks run after UI events have been processed in Choreographer.java 2) when we
 * enqueue a runnable on the JS queue thread, it won't be called until after any previously enqueued
 * JS jobs have finished processing
 *
 * <p>If JS is taking a long time processing events, then the UI events generated on the UI thread
 * can be coalesced into fewer events so that when the runnable runs, we don't overload JS with a
 * ton of events and make it get even farther behind.
 *
 * <p>Ideally, we don't need this and JS is fast enough to process all the events each frame, but
 * bad things happen, including load on CPUs from the system, and we should handle this case well.
 *
 * <p>== Event Cookies ==
 *
 * <p>An event cookie is made up of the event type id, view tag, and a custom coalescing key. Only
 * Events that have the same cookie can be coalesced.
 *
 * <p>Event Cookie Composition: VIEW_TAG_MASK = 0x00000000ffffffff EVENT_TYPE_ID_MASK =
 * 0x0000ffff00000000 COALESCING_KEY_MASK = 0xffff000000000000
 */
public class EventDispatcher implements LifecycleEventListener {

  private static final Comparator<Event> EVENT_COMPARATOR =
      new Comparator<Event>() {
        @Override
        public int compare(Event lhs, Event rhs) {
          if (lhs == null && rhs == null) {
            return 0;
          }
          if (lhs == null) {
            return -1;
          }
          if (rhs == null) {
            return 1;
          }

          long diff = lhs.getTimestampMs() - rhs.getTimestampMs();
          if (diff == 0) {
            return 0;
          } else if (diff < 0) {
            return -1;
          } else {
            return 1;
          }
        }
      };

  private final Object mEventsStagingLock = new Object();
  private final Object mEventsToDispatchLock = new Object();
  private final ReactApplicationContext mReactContext;
  private final LongSparseArray<Integer> mEventCookieToLastEventIdx = new LongSparseArray<>();
  private final Map<String, Short> mEventNameToEventId = MapBuilder.newHashMap();
  private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable();
  private final ArrayList<Event> mEventStaging = new ArrayList<>();

  private Event[] mEventsToDispatch = new Event[16];
  private int mEventsToDispatchSize = 0;
  private volatile @Nullable RCTEventEmitter mRCTEventEmitter;
  private final ScheduleDispatchFrameCallback mCurrentFrameCallback;
  private short mNextEventTypeId = 0;
  private volatile boolean mHasDispatchScheduled = false;
  private volatile int mHasDispatchScheduledCount = 0;

  public EventDispatcher(ReactApplicationContext reactContext) {
    mReactContext = reactContext;
    mReactContext.addLifecycleEventListener(this);
    mCurrentFrameCallback = new ScheduleDispatchFrameCallback();
  }

  /** Sends the given Event to JS, coalescing eligible events if JS is backed up. */
  public void dispatchEvent(Event event) {
    Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized");
    synchronized (mEventsStagingLock) {
      mEventStaging.add(event);
      Systrace.startAsyncFlow(
          Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID());
    }
    if (mRCTEventEmitter != null) {
      // If the host activity is paused, the frame callback may not be currently
      // posted. Ensure that it is so that this event gets delivered promptly.
      mCurrentFrameCallback.maybePostFromNonUI();
    } else {
      // No JS application has started yet, or resumed. This can happen when a ReactRootView is
      // added to view hierarchy, but ReactContext creation has not completed yet. In this case, any
      // touch event dispatch will hit this codepath, and we simply queue them so that they
      // are dispatched once ReactContext creation completes and JS app is running.
    }
  }

  @Override
  public void onHostResume() {
    UiThreadUtil.assertOnUiThread();
    if (mRCTEventEmitter == null) {
      mRCTEventEmitter = mReactContext.getJSModule(RCTEventEmitter.class);
    }
    mCurrentFrameCallback.maybePost();
  }

  @Override
  public void onHostPause() {
    stopFrameCallback();
  }

  @Override
  public void onHostDestroy() {
    stopFrameCallback();
  }

  public void onCatalystInstanceDestroyed() {
    stopFrameCallback();
  }

  private void stopFrameCallback() {
    UiThreadUtil.assertOnUiThread();
    mCurrentFrameCallback.stop();
  }

  /**
   * We use a staging data structure so that all UI events generated in a single frame are
   * dispatched at once. Otherwise, a JS runnable enqueued in a previous frame could run while the
   * UI thread is in the process of adding UI events and we might incorrectly send one event this
   * frame and another from this frame during the next.
   */
  private void moveStagedEventsToDispatchQueue() {
    synchronized (mEventsStagingLock) {
      synchronized (mEventsToDispatchLock) {
        for (int i = 0; i < mEventStaging.size(); i++) {
          Event event = mEventStaging.get(i);

          if (!event.canCoalesce()) {
            addEventToEventsToDispatch(event);
            continue;
          }

          long eventCookie =
              getEventCookie(event.getViewTag(), event.getEventName(), event.getCoalescingKey());

          Event eventToAdd = null;
          Event eventToDispose = null;
          Integer lastEventIdx = mEventCookieToLastEventIdx.get(eventCookie);

          if (lastEventIdx == null) {
            eventToAdd = event;
            mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize);
          } else {
            Event lastEvent = mEventsToDispatch[lastEventIdx];
            Event coalescedEvent = event.coalesce(lastEvent);
            if (coalescedEvent != lastEvent) {
              eventToAdd = coalescedEvent;
              mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize);
              eventToDispose = lastEvent;
              mEventsToDispatch[lastEventIdx] = null;
            } else {
              eventToDispose = event;
            }
          }

          if (eventToAdd != null) {
            addEventToEventsToDispatch(eventToAdd);
          }
          if (eventToDispose != null) {
            eventToDispose.dispose();
          }
        }
      }
      mEventStaging.clear();
    }
  }

  private long getEventCookie(int viewTag, String eventName, short coalescingKey) {
    short eventTypeId;
    Short eventIdObj = mEventNameToEventId.get(eventName);
    if (eventIdObj != null) {
      eventTypeId = eventIdObj;
    } else {
      eventTypeId = mNextEventTypeId++;
      mEventNameToEventId.put(eventName, eventTypeId);
    }
    return getEventCookie(viewTag, eventTypeId, coalescingKey);
  }

  private static long getEventCookie(int viewTag, short eventTypeId, short coalescingKey) {
    return viewTag
        | (((long) eventTypeId) & 0xffff) << 32
        | (((long) coalescingKey) & 0xffff) << 48;
  }

  private class ScheduleDispatchFrameCallback implements Choreographer.FrameCallback {
    private volatile boolean mIsPosted = false;
    private boolean mShouldStop = false;

    @Override
    public void doFrame(long frameTimeNanos) {
      UiThreadUtil.assertOnUiThread();

      if (mShouldStop) {
        mIsPosted = false;
      } else {
        post();
      }

      Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ScheduleDispatchFrameCallback");
      try {
        moveStagedEventsToDispatchQueue();

        if (mEventsToDispatchSize > 0 && !mHasDispatchScheduled) {
          mHasDispatchScheduled = true;
          Systrace.startAsyncFlow(
              Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
              "ScheduleDispatchFrameCallback",
              mHasDispatchScheduledCount);
          mReactContext.runOnJSQueueThread(mDispatchEventsRunnable);
        }
      } finally {
        Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    }

    public void stop() {
      mShouldStop = true;
    }

    public void maybePost() {
      if (!mIsPosted) {
        mIsPosted = true;
        post();
      }
    }

    private void post() {
      ReactChoreographer.getInstance()
          .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback);
    }

    public void maybePostFromNonUI() {
      if (mIsPosted) {
        return;
      }

      // We should only hit this slow path when we receive events while the host activity is paused.
      if (mReactContext.isOnUiQueueThread()) {
        maybePost();
      } else {
        mReactContext.runOnUiQueueThread(
            new Runnable() {
              @Override
              public void run() {
                maybePost();
              }
            });
      }
    }
  }

  private class DispatchEventsRunnable implements Runnable {

    @Override
    public void run() {
      Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchEventsRunnable");
      try {
        Systrace.endAsyncFlow(
            Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
            "ScheduleDispatchFrameCallback",
            mHasDispatchScheduledCount);
        mHasDispatchScheduled = false;
        mHasDispatchScheduledCount++;
        Assertions.assertNotNull(mRCTEventEmitter);
        synchronized (mEventsToDispatchLock) {
          // We avoid allocating an array and iterator, and "sorting" if we don't need to.
          // This occurs when the size of mEventsToDispatch is zero or one.
          if (mEventsToDispatchSize > 1) {
            Arrays.sort(mEventsToDispatch, 0, mEventsToDispatchSize, EVENT_COMPARATOR);
          }
          for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) {
            Event event = mEventsToDispatch[eventIdx];
            // Event can be null if it has been coalesced into another event.
            if (event == null) {
              continue;
            }
            Systrace.endAsyncFlow(
                Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID());
            event.dispatch(mRCTEventEmitter);
            event.dispose();
          }
          clearEventsToDispatch();
          mEventCookieToLastEventIdx.clear();
        }
      } finally {
        Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    }
  }

  private void addEventToEventsToDispatch(Event event) {
    if (mEventsToDispatchSize == mEventsToDispatch.length) {
      mEventsToDispatch = Arrays.copyOf(mEventsToDispatch, 2 * mEventsToDispatch.length);
    }
    mEventsToDispatch[mEventsToDispatchSize++] = event;
  }

  private void clearEventsToDispatch() {
    Arrays.fill(mEventsToDispatch, 0, mEventsToDispatchSize, null);
    mEventsToDispatchSize = 0;
  }
}
 @Override
 public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
   return MapBuilder.of(
       TabSelectedEvent.EVENT_NAME, (Object) MapBuilder.of("registrationName", "onTabSelected"));
 }
 @Override
 public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
   return MapBuilder.<String, Object>builder()
       .put("topRefresh", MapBuilder.of("registrationName", "onRefresh"))
       .build();
 }