@ReactMethod public void setHidden(final boolean hidden) { final Activity activity = getCurrentActivity(); if (activity == null) { FLog.w(ReactConstants.TAG, "StatusBarModule: Ignored status bar change, current activity is null."); return; } UiThreadUtil.runOnUiThread( new Runnable() { @Override public void run() { if (hidden) { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); } else { activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } } }); }
@ReactMethod public void setStyle(final String style) { final Activity activity = getCurrentActivity(); if (activity == null) { FLog.w(ReactConstants.TAG, "StatusBarModule: Ignored status bar change, current activity is null."); return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { UiThreadUtil.runOnUiThread( new Runnable() { @TargetApi(Build.VERSION_CODES.M) @Override public void run() { View decorView = activity.getWindow().getDecorView(); decorView.setSystemUiVisibility( style.equals("dark-content") ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0); } } ); } }
@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 static JSONObject toJSON(View view) throws JSONException { UiThreadUtil.assertOnUiThread(); JSONObject result = new JSONObject(); result.put("n", view.getClass().getName()); result.put("i", System.identityHashCode(view)); Object tag = view.getTag(); if (tag != null && tag instanceof String) { result.put("t", tag); } if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; if (viewGroup.getChildCount() > 0) { JSONArray children = new JSONArray(); for (int i = 0; i < viewGroup.getChildCount(); i++) { children.put(i, toJSON(viewGroup.getChildAt(i))); } result.put("c", children); } } return result; }
/** * @return a MessageQueueThreadImpl corresponding to Android's main UI thread. */ private static MessageQueueThreadImpl createForMainThread( String name, QueueThreadExceptionHandler exceptionHandler) { Looper mainLooper = Looper.getMainLooper(); final MessageQueueThreadImpl mqt = new MessageQueueThreadImpl(name, mainLooper, exceptionHandler); if (UiThreadUtil.isOnUiThread()) { Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); MessageQueueThreadRegistry.register(mqt); } else { UiThreadUtil.runOnUiThread( new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); MessageQueueThreadRegistry.register(mqt); } }); } return mqt; }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension( MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); mWasMeasured = true; // Check if we were waiting for onMeasure to attach the root view if (mReactInstanceManager != null && !mIsAttachedToInstance) { // Enqueue it to UIThread not to block onMeasure waiting for the catalyst instance creation UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { attachToReactInstanceManager(); } }); } }
@Override public void handleReloadJS() { UiThreadUtil.assertOnUiThread(); // dismiss redbox if exists if (mRedBoxDialog != null) { mRedBoxDialog.dismiss(); } if (mDevSettings.isRemoteJSDebugEnabled()) { mDevLoadingViewController.showForRemoteJSEnabled(); mDevLoadingViewVisible = true; reloadJSInProxyMode(); } else { String bundleURL = mDevServerHelper.getDevServerBundleURL(Assertions.assertNotNull(mJSAppBundleName)); reloadJSFromServer(bundleURL); } }
/** * Returns the coordinates of a view relative to the window (not just the RootView * which is what measure will return) * * @param tag - the tag for the view * @param outputBuffer - output buffer that contains [x,y,width,height] of the view in coordinates * relative to the device window */ public void measureInWindow(int tag, int[] outputBuffer) { UiThreadUtil.assertOnUiThread(); View v = mTagsToViews.get(tag); if (v == null) { throw new NoSuchNativeViewException("No native view for " + tag + " currently exists"); } v.getLocationOnScreen(outputBuffer); // We need to remove the status bar from the height. getLocationOnScreen will include the // status bar. Resources resources = v.getContext().getResources(); int statusBarId = resources.getIdentifier("status_bar_height", "dimen", "android"); if (statusBarId > 0) { int height = (int) resources.getDimension(statusBarId); outputBuffer[1] -= height; } // outputBuffer[0,1] already contain what we want outputBuffer[2] = v.getWidth(); outputBuffer[3] = v.getHeight(); }
public void updateProgress(final @Nullable String status, final @Nullable Integer done, final @Nullable Integer total) { if (!sEnabled) { return; } UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { StringBuilder message = new StringBuilder(); message.append(status != null ? status : "Loading"); if (done != null && total != null && total > 0) { message.append(String.format(Locale.getDefault(), " %.1f%% (%d/%d)", (float) done / total * 100, done, total)); } message.append("\u2026"); // `...` character mDevLoadingView.setText(message); } }); }
@Override public void onEventDispatch(Event event) { // Only support events dispatched from the UI thread. if (!UiThreadUtil.isOnUiThread()) { return; } if (!mEventDrivers.isEmpty()) { // If the event has a different name in native convert it to it's JS name. String eventName = event.getEventName(); Map<String, String> customEventType = mCustomEventTypes.get(eventName); if (customEventType != null) { eventName = customEventType.get("registrationName"); } List<EventAnimationDriver> driversForKey = mEventDrivers.get(event.getViewTag() + eventName); if (driversForKey != null) { for (EventAnimationDriver driver : driversForKey) { event.dispatch(driver); mRunUpdateNodeList.add(driver.mValueNode); } updateNodes(mRunUpdateNodeList); mRunUpdateNodeList.clear(); } } }
/** * Releases all references to given native View. */ protected void dropView(View view) { UiThreadUtil.assertOnUiThread(); if (!mRootTags.get(view.getId())) { // For non-root views we notify viewmanager with {@link ViewManager#onDropInstance} resolveViewManager(view.getId()).onDropViewInstance(view); } ViewManager viewManager = mTagsToViewManagers.get(view.getId()); if (view instanceof ViewGroup && viewManager instanceof ViewGroupManager) { ViewGroup viewGroup = (ViewGroup) view; ViewGroupManager viewGroupManager = (ViewGroupManager) viewManager; for (int i = viewGroupManager.getChildCount(viewGroup) - 1; i >= 0; i--) { View child = viewGroupManager.getChildAt(viewGroup, i); if (mTagsToViews.get(child.getId()) != null) { dropView(child); } } viewGroupManager.removeAllViews(viewGroup); } mTagsToViews.remove(view.getId()); mTagsToViewManagers.remove(view.getId()); }
/** * Returns true on success, false on failure. If successful, after calling, output buffer will be * {x, y, width, height}. */ public void measure(int tag, int[] outputBuffer) { UiThreadUtil.assertOnUiThread(); View v = mTagsToViews.get(tag); if (v == null) { throw new NoSuchNativeViewException("No native view for " + tag + " currently exists"); } View rootView = (View) RootViewUtil.getRootView(v); // It is possible that the RootView can't be found because this view is no longer on the screen // and has been removed by clipping if (rootView == null) { throw new NoSuchNativeViewException("Native view " + tag + " is no longer on screen"); } rootView.getLocationInWindow(outputBuffer); int rootX = outputBuffer[0]; int rootY = outputBuffer[1]; v.getLocationInWindow(outputBuffer); outputBuffer[0] = outputBuffer[0] - rootX; outputBuffer[1] = outputBuffer[1] - rootY; outputBuffer[2] = v.getWidth(); outputBuffer[3] = v.getHeight(); }
/** * Show a {@link 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 success will be called with the position of the selected item as the first argument, or * no arguments if the menu is dismissed */ public void showPopupMenu(int reactTag, ReadableArray items, Callback success) { UiThreadUtil.assertOnUiThread(); View anchor = mTagsToViews.get(reactTag); if (anchor == null) { throw new JSApplicationIllegalArgumentException("Could not find view with tag " + reactTag); } PopupMenu popupMenu = new PopupMenu(getReactContextForView(reactTag), anchor); Menu menu = popupMenu.getMenu(); for (int i = 0; i < items.size(); i++) { menu.add(Menu.NONE, Menu.NONE, i, items.getString(i)); } PopupMenuCallbackHandler handler = new PopupMenuCallbackHandler(success); popupMenu.setOnMenuItemClickListener(handler); popupMenu.setOnDismissListener(handler); popupMenu.show(); }
/** * Update layout of given view, via immediate update or animation depending on the current batch * layout animation configuration supplied during initialization. Handles create and update * animations. * * @param view the view to update layout of * @param x the new X position for the view * @param y the new Y position for the view * @param width the new width value for the view * @param height the new height value for the view */ public void applyLayoutUpdate(View view, int x, int y, int width, int height) { UiThreadUtil.assertOnUiThread(); // Determine which animation to use : if view is initially invisible, use create animation, // otherwise use update animation. This approach is easier than maintaining a list of tags // for recently created views. AbstractLayoutAnimation layoutAnimation = (view.getWidth() == 0 || view.getHeight() == 0) ? mLayoutCreateAnimation : mLayoutUpdateAnimation; Animation animation = layoutAnimation.createAnimation(view, x, y, width, height); if (animation == null || !(animation instanceof HandleLayout)) { view.layout(x, y, x + width, y + height); } if (animation != null) { view.startAnimation(animation); } }
/** * Find touch event target view within the provided container given the coordinates provided * via {@link MotionEvent}. * * @param eventX the X screen coordinate of the touch location * @param eventY the Y screen coordinate of the touch location * @param viewGroup the container view to traverse * @param viewCoords an out parameter that will return the X,Y value in the target view * @param nativeViewTag an out parameter that will return the native view id * @return the react tag ID of the child view that should handle the event */ public static int findTargetTagAndCoordinatesForTouch( float eventX, float eventY, ViewGroup viewGroup, float[] viewCoords, @Nullable int[] nativeViewTag) { UiThreadUtil.assertOnUiThread(); int targetTag = viewGroup.getId(); // Store eventCoords in array so that they are modified to be relative to the targetView found. viewCoords[0] = eventX; viewCoords[1] = eventY; View nativeTargetView = findTouchTargetView(viewCoords, viewGroup); if (nativeTargetView != null) { View reactTargetView = findClosestReactAncestor(nativeTargetView); if (reactTargetView != null) { if (nativeViewTag != null) { nativeViewTag[0] = reactTargetView.getId(); } targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]); } } return targetTag; }
public void shutDownContext() { if (mInstance != null) { final ReactContext contextToDestroy = mReactContext; mReactContext = null; mInstance = null; final SimpleSettableFuture<Void> semaphore = new SimpleSettableFuture<>(); UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { if (contextToDestroy != null) { contextToDestroy.destroy(); } semaphore.set(null); } }); semaphore.getOrThrow(); } }
/** * Finish a JS task. Doesn't actually stop the task on the JS side, only removes it from the list * of active tasks and notifies listeners. A task can only be finished once. * * @param taskId the unique id returned by {@link #startTask}. */ public synchronized void finishTask(final int taskId) { Assertions.assertCondition( mActiveTasks.remove(taskId), "Tried to finish non-existent task with id " + taskId + "."); Runnable timeout = mTaskTimeouts.get(taskId); if (timeout != null) { mHandler.removeCallbacks(timeout); mTaskTimeouts.remove(taskId); } UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { for (HeadlessJsTaskEventListener listener : mHeadlessJsTaskEventListeners) { listener.onHeadlessJsTaskFinish(taskId); } } }); }
/** * Start a JS task. Handles invoking {@link AppRegistry#startHeadlessTask} and notifying * listeners. * * @return a unique id representing this task instance. */ public synchronized int startTask(final HeadlessJsTaskConfig taskConfig) { UiThreadUtil.assertOnUiThread(); ReactContext reactContext = Assertions.assertNotNull( mReactContext.get(), "Tried to start a task on a react context that has already been destroyed"); if (reactContext.getLifecycleState() == LifecycleState.RESUMED && !taskConfig.isAllowedInForeground()) { throw new IllegalStateException( "Tried to start task " + taskConfig.getTaskKey() + " while in foreground, but this is not allowed."); } final int taskId = mLastTaskId.incrementAndGet(); mActiveTasks.add(taskId); reactContext.getJSModule(AppRegistry.class) .startHeadlessTask(taskId, taskConfig.getTaskKey(), taskConfig.getData()); if (taskConfig.getTimeout() > 0) { scheduleTaskTimeout(taskId, taskConfig.getTimeout()); } for (HeadlessJsTaskEventListener listener : mHeadlessJsTaskEventListeners) { listener.onHeadlessJsTaskStart(taskId); } return taskId; }
/** * Timing module needs to be created on the main thread so that it gets the correct Choreographer. */ protected Timing createTimingModule() { final SimpleSettableFuture<Timing> simpleSettableFuture = new SimpleSettableFuture<Timing>(); UiThreadUtil.runOnUiThread( new Runnable() { @Override public void run() { Timing timing = new Timing(getContext(), mock(DevSupportManager.class)); simpleSettableFuture.set(timing); } }); try { return simpleSettableFuture.get(5000, TimeUnit.MILLISECONDS); } catch (Exception e) { throw new RuntimeException(e); } }
protected static void initializeJavaModule(final BaseJavaModule javaModule) { final Semaphore semaphore = new Semaphore(0); UiThreadUtil.runOnUiThread( new Runnable() { @Override public void run() { javaModule.initialize(); if (javaModule instanceof LifecycleEventListener) { ((LifecycleEventListener) javaModule).onHostResume(); } semaphore.release(); } }); try { SoftAssertions.assertCondition( semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS), "Timed out initializing timing module"); } catch (InterruptedException e) { throw new RuntimeException(e); } }
/** * Waits for both the UI thread and bridge to be idle. It determines this by waiting for the * bridge to become idle, then waiting for the UI thread to become idle, then checking if the * bridge is idle again (if the bridge was idle before and is still idle after running the UI * thread to idle, then there are no more events to process in either place). * <p/> * Also waits for any Choreographer callbacks to run after the initial sync since things like UI * events are initiated from Choreographer callbacks. */ public static void waitForBridgeAndUIIdle( ReactBridgeIdleSignaler idleSignaler, final ReactContext reactContext, long timeoutMs) { UiThreadUtil.assertNotOnUiThread(); long startTime = SystemClock.uptimeMillis(); waitInner(idleSignaler, timeoutMs); long timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime)); waitForChoreographer(timeToWait); waitForJSIdle(reactContext); timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime)); waitInner(idleSignaler, timeToWait); timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime)); waitForChoreographer(timeToWait); }
public void showMessage(final String message, final int color, final int backgroundColor) { if (!sEnabled) { return; } UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { mDevLoadingView.setBackgroundColor(backgroundColor); mDevLoadingView.setText(message); mDevLoadingView.setTextColor(color); setVisible(true); } }); }
public void addRootView( final int tag, final SizeMonitoringFrameLayout rootView, final ThemedReactContext themedRootContext) { if (UiThreadUtil.isOnUiThread()) { mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext); } else { final Semaphore semaphore = new Semaphore(0); mReactApplicationContext.runOnUiQueueThread( new Runnable() { @Override public void run() { mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext); semaphore.release(); } }); try { SoftAssertions.assertCondition( semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS), "Timed out adding root view"); } catch (InterruptedException e) { throw new RuntimeException(e); } } }