/** * Copyright (c) Facebook, Inc. and its affiliates. * *
This source code is licensed under the MIT license found in the LICENSE file in the root * directory of this source tree. */ package com.facebook.react.uimanager; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_END; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_START; import static com.facebook.react.uimanager.common.UIManagerType.DEFAULT; import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; import android.content.ComponentCallbacks2; import android.content.Context; import android.content.res.Configuration; import android.media.AudioManager; import android.util.ArrayMap; import android.view.View; import com.facebook.common.logging.FLog; import com.facebook.debug.holder.PrinterHolder; import com.facebook.debug.tags.ReactDebugOverlayTags; import com.facebook.react.animation.Animation; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.GuardedRunnable; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.OnBatchCompleteListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UIManager; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.common.MeasureSpecProvider; import com.facebook.react.uimanager.common.ViewUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.RCTEventEmitter; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.Nullable; /** * Native module to allow JS to create and update native Views. * *
* *
To facilitate this, this module enqueues operations that are then applied to native view * hierarchy through {@link NativeViewHierarchyManager} at the end of each transaction. * *
* *
Note that this must be called after getWidth()/getHeight() actually return something. See * CatalystApplicationFragment as an example. * *
TODO(6242243): Make addRootView thread safe NB: this method is horribly not-thread-safe.
*/
@Override
public NB: Unlike {@link #measure}, this will measure relative to the view layout, not the visible
* window which can cause unexpected results when measuring relative to things like ScrollViews
* that can have offset content on the screen.
*/
@ReactMethod
public void measureLayout(
int tag, int ancestorTag, Callback errorCallback, Callback successCallback) {
mUIImplementation.measureLayout(tag, ancestorTag, errorCallback, successCallback);
}
/**
* Like {@link #measure} and {@link #measureLayout} but measures relative to the immediate parent.
*
* NB: Unlike {@link #measure}, this will measure relative to the view layout, not the visible
* window which can cause unexpected results when measuring relative to things like ScrollViews
* that can have offset content on the screen.
*/
@ReactMethod
public void measureLayoutRelativeToParent(
int tag, Callback errorCallback, Callback successCallback) {
mUIImplementation.measureLayoutRelativeToParent(tag, errorCallback, successCallback);
}
/**
* Find the touch target child native view in the supplied root view hierarchy, given a react
* target location.
*
* This method is currently used only by Element Inspector DevTool.
*
* @param reactTag the tag of the root view to traverse
* @param point an array containing both X and Y target location
* @param callback will be called if with the identified child view react ID, and measurement
* info. If no view was found, callback will be invoked with no data.
*/
@ReactMethod
public void findSubviewIn(
final int reactTag, final ReadableArray point, final Callback callback) {
mUIImplementation.findSubviewIn(
reactTag,
Math.round(PixelUtil.toPixelFromDIP(point.getDouble(0))),
Math.round(PixelUtil.toPixelFromDIP(point.getDouble(1))),
callback);
}
/** Check if the first shadow node is the descendant of the second shadow node */
@ReactMethod
public void viewIsDescendantOf(
final int reactTag, final int ancestorReactTag, final Callback callback) {
mUIImplementation.viewIsDescendantOf(reactTag, ancestorReactTag, callback);
}
/** Registers a new Animation that can then be added to a View using {@link #addAnimation}. */
public void registerAnimation(Animation animation) {
mUIImplementation.registerAnimation(animation);
}
/**
* Adds an Animation previously registered with {@link #registerAnimation} to a View and starts it
*/
public void addAnimation(int reactTag, int animationID, Callback onSuccess) {
mUIImplementation.addAnimation(reactTag, animationID, onSuccess);
}
/** Removes an existing Animation, canceling it if it was in progress. */
public void removeAnimation(int reactTag, int animationID) {
mUIImplementation.removeAnimation(reactTag, animationID);
}
@Override
@ReactMethod
public void setJSResponder(int reactTag, boolean blockNativeResponder) {
mUIImplementation.setJSResponder(reactTag, blockNativeResponder);
}
@Override
@ReactMethod
public void clearJSResponder() {
mUIImplementation.clearJSResponder();
}
@ReactMethod
public void dispatchViewManagerCommand(
int reactTag, int commandId, @Nullable ReadableArray commandArgs) {
// TODO: this is a temporary approach to support ViewManagerCommands in Fabric until
// the dispatchViewManagerCommand() method is supported by Fabric JS API.
UIManagerHelper.getUIManager(getReactApplicationContext(), ViewUtil.getUIManagerType(reactTag))
.dispatchCommand(reactTag, commandId, commandArgs);
}
@Override
public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray commandArgs) {
mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
}
@ReactMethod
public void playTouchSound() {
AudioManager audioManager =
(AudioManager) getReactApplicationContext().getSystemService(Context.AUDIO_SERVICE);
if (audioManager != null) {
audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
}
}
/**
* Show a PopupMenu.
*
* @param reactTag the tag of the anchor view (the PopupMenu is displayed next to this view); this
* needs to be the tag of a native view (shadow views can not be anchors)
* @param items the menu items as an array of strings
* @param error will be called if there is an error displaying the menu
* @param success will be called with the position of the selected item as the first argument, or
* no arguments if the menu is dismissed
*/
@ReactMethod
public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) {
mUIImplementation.showPopupMenu(reactTag, items, error, success);
}
@ReactMethod
public void dismissPopupMenu() {
mUIImplementation.dismissPopupMenu();
}
/**
* LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled
* explicitly in order to avoid regression in existing application written for iOS using this API.
*
* Warning : This method will be removed in future version of React Native, and layout
* animation will be enabled by default, so always check for its existence before invoking it.
*
* TODO(9139831) : remove this method once layout animation is fully stable.
*
* @param enabled whether layout animation is enabled or not
*/
@ReactMethod
public void setLayoutAnimationEnabledExperimental(boolean enabled) {
mUIImplementation.setLayoutAnimationEnabledExperimental(enabled);
}
/**
* Configure an animation to be used for the native layout changes, and native views creation. The
* animation will only apply during the current batch operations.
*
* TODO(7728153) : animating view deletion is currently not supported. TODO(7613721) :
* callbacks are not supported, this feature will likely be killed.
*
* @param config the configuration of the animation for view addition/removal/update.
* @param success will be called when the animation completes, or when the animation get
* interrupted. In this case, callback parameter will be false.
* @param error will be called if there was an error processing the animation
*/
@ReactMethod
public void configureNextLayoutAnimation(ReadableMap config, Callback success, Callback error) {
mUIImplementation.configureNextLayoutAnimation(config, success, error);
}
/**
* To implement the transactional requirement mentioned in the class javadoc, we only commit UI
* changes to the actual view hierarchy once a batch of JS->Java calls have been completed. We
* know this is safe because all JS->Java calls that are triggered by a Java->JS call (e.g. the
* delivery of a touch event or execution of 'renderApplication') end up in a single JS->Java
* transaction.
*
* A better way to do this would be to have JS explicitly signal to this module when a UI
* transaction is done. Right now, though, this is how iOS does it, and we should probably update
* the JS and native code and make this change at the same time.
*
* TODO(5279396): Make JS UI library explicitly notify the native UI module of the end of a UI
* transaction using a standard native call
*/
@Override
public void onBatchComplete() {
int batchId = mBatchId;
mBatchId++;
SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchCompleteUI")
.arg("BatchId", batchId)
.flush();
for (UIManagerModuleListener listener : mListeners) {
listener.willDispatchViewUpdates(this);
}
try {
mUIImplementation.dispatchViewUpdates(batchId);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
public void setViewHierarchyUpdateDebugListener(
@Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) {
mUIImplementation.setViewHierarchyUpdateDebugListener(listener);
}
public EventDispatcher getEventDispatcher() {
return mEventDispatcher;
}
@ReactMethod
public void sendAccessibilityEvent(int tag, int eventType) {
mUIImplementation.sendAccessibilityEvent(tag, eventType);
}
/**
* Schedule a block to be executed on the UI thread. Useful if you need to execute view logic
* after all currently queued view updates have completed.
*
* @param block that contains UI logic you want to execute.
* Usage Example:
* UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
* uiManager.addUIBlock(new UIBlock() { public void execute (NativeViewHierarchyManager nvhm)
* { View view = nvhm.resolveView(tag); // ...execute your code on View (e.g. snapshot the
* view) } });
*/
public void addUIBlock(UIBlock block) {
mUIImplementation.addUIBlock(block);
}
/**
* Schedule a block to be executed on the UI thread. Useful if you need to execute view logic
* before all currently queued view updates have completed.
*
* @param block that contains UI logic you want to execute.
*/
public void prependUIBlock(UIBlock block) {
mUIImplementation.prependUIBlock(block);
}
public void addUIManagerListener(UIManagerModuleListener listener) {
mListeners.add(listener);
}
public void removeUIManagerListener(UIManagerModuleListener listener) {
mListeners.remove(listener);
}
/**
* Given a reactTag from a component, find its root node tag, if possible. Otherwise, this will
* return 0. If the reactTag belongs to a root node, this will return the same reactTag.
*
* @param reactTag the component tag
* @return the rootTag
*/
public int resolveRootTagFromReactTag(int reactTag) {
return ViewUtil.isRootTag(reactTag)
? reactTag
: mUIImplementation.resolveRootTagFromReactTag(reactTag);
}
/** Dirties the node associated with the given react tag */
public void invalidateNodeLayout(int tag) {
ReactShadowNode node = mUIImplementation.resolveShadowNode(tag);
if (node == null) {
FLog.w(
ReactConstants.TAG,
"Warning : attempted to dirty a non-existent react shadow node. reactTag=" + tag);
return;
}
node.dirty();
mUIImplementation.dispatchViewUpdates(-1);
}
/**
* Updates the styles of the {@link ReactShadowNode} based on the Measure specs received by
* parameters.
*/
public void updateRootLayoutSpecs(
final int rootViewTag, final int widthMeasureSpec, final int heightMeasureSpec) {
ReactApplicationContext reactApplicationContext = getReactApplicationContext();
reactApplicationContext.runOnNativeModulesQueueThread(
new GuardedRunnable(reactApplicationContext) {
@Override
public void runGuarded() {
mUIImplementation.updateRootView(rootViewTag, widthMeasureSpec, heightMeasureSpec);
mUIImplementation.dispatchViewUpdates(-1);
}
});
}
/** Listener that drops the CSSNode pool on low memory when the app is backgrounded. */
private class MemoryTrimCallback implements ComponentCallbacks2 {
@Override
public void onTrimMemory(int level) {
if (level >= TRIM_MEMORY_MODERATE) {
YogaNodePool.get().clear();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {}
@Override
public void onLowMemory() {}
}
}