UI Hider
UI Hider covers selected parts of an app. The other parts of the app stay visible and usable.
For example, UI Hider can cover a recommendation feed or an Explore tab.
Turn on UI Hider
Tap Reducers. Then, tap UI Hider and turn on Enable UI Hider.
Use a built-in rule
Curbox includes rules for common distracting interface elements. Turn on each rule that you want to use.
Instagram rules
| Rule | Result |
|---|---|
| Hide home feed except the following tab | Covers the main Home feed. The Following feed and other features stay available. |
| Hide explore tab | Covers the Explore tab. |
To open the Following feed, tap the Instagram wordmark at the top of the screen. Then, tap Following.
YouTube rules
| Rule | Result |
|---|---|
| Hide everything except the video | Covers recommendations, comments, and the description below the video. |
| Hide feed and only let me access search results | Covers the Home feed. Search results stay available. |
X rules
| Rule | Result |
|---|---|
| Hide “For You” and only let me access the Following tab | Covers the For You feed. The Following feed stays available. |
Pinterestcd rules
| Rule | Result |
|---|---|
| Hide “For You” and only let me access the Following tab | Covers the For You feed. The Following feed stays available. |
Create a custom script
Use a custom script when a built-in rule does not cover the required element. A script can find Android accessibility nodes and cover their screen areas.
You need basic programming knowledge to create a script. A script can use variables, conditions, loops, functions, and persistent values.
-
Open UI Hider Tap Reducers. Then, tap UI Hider.
-
Turn on UI Hider Turn on the main switch.
-
Create a script Tap Add script.
-
Enter the app package Enter the Android package name for the target app.
-
Enter the script Add the script in the editor.
-
Save the script Tap Save. Curbox reports a syntax error if the script is not valid.
How a script runs
- Each script is connected to one Android app package.
- Curbox runs the script only while that app is in the foreground.
- Curbox runs the script again after screen content or navigation changes.
- Each run has time, operation, node, recursion, and overlay limits.
- Curbox stops a run that exceeds a limit.
- Curbox redraws overlays after each run.
- An overlay disappears if the next run does not draw it.
You do not have to remove an old overlay in the script.
First example
This script covers the Instagram Home feed. It keeps the stories, top bar, and bottom navigation visible.
# Only act inside Instagramif app != "com.instagram.android" { return}
top = find(id="com.instagram.android:id/action_bar_container")nav = find(id="com.instagram.android:id/tab_bar")
# Cover the area between the two anchorsif top != null and nav != null { y = top.bottom height = nav.top - y if height > 0 { draw(0, y, screen.width, height) }}The script finds two reference nodes. It reads their screen positions and covers the area between them.
Language reference
Comments
Use # to start a comment. Curbox ignores all text after # on that line.
# This is a commentx = 5 # This is also a commentValue types
The scripting language uses dynamic types.
| Type | Examples or function |
|---|---|
| Number | 5, 3.14, -200 |
| String | "hello", "com.instagram.android" |
| Boolean | true, false |
| Null | null means that there is no value |
| List | [1, 2, 3] or the result from findAll(...) |
| Node | An interface element from find(), root(), or a related function |
All numbers use a decimal representation internally. Strings support \n, \t, \", and \\ escape sequences.
Variables
Use = or := to assign a value. You do not declare a variable before its first assignment.
x = 10name = "feed"x = x + 1Operators
| Type | Operators |
|---|---|
| Arithmetic | +, -, *, /, % |
| Comparison | ==, !=, <, <=, >, >= |
| Logical | and, or, not |
| Grouping | ( ... ) |
The + operator joins values if either value is a string. For example, "y=" + 5 returns "y=5".
The and and or operators use short-circuit evaluation. Only false and null are false values. All other values are true values, including 0 and "".
Lists and ranges
List indexes start at 0.
nums = [10, 20, 30]first = nums[0]count = len(nums)A range includes its start value and excludes its end value. Thus, 0..3 contains 0, 1, and 2.
for i in 0..3 { log(i)}Conditions
if x > 100 { log("big")} else if x > 10 { log("medium")} else { log("small")}Loops
Use a range for a count-based loop:
for i in 0..10 { log(i)}Use a list to process matched nodes:
buttons = findAll(class="android.widget.Button")for button in buttons { log(button.text)}Use while for a condition-based loop:
i = 0while i < 5 { i = i + 1}Use break to stop a loop. Use continue to start the next iteration.
Functions
Use fn to define a function.
fn area(node) { return node.w * node.h}
banner = find(id="com.app:id/banner")if banner != null and area(banner) > 50000 { hide(banner)}A function returns null if it reaches the end or uses return without a value. A top-level return stops the script.
Runtime API
Context values
Each script can use these values:
| Value | Content |
|---|---|
app | Package name of the foreground app |
screen.width | Screen width in pixels |
screen.height | Screen height in pixels |
event.type | Event type: window_state, content, scrolled, clicked, selected, or other |
event.package | The same package name as app |
event.text | Event text, or null |
event.class | Class name of the event source, or null |
Example:
if event.type == "scrolled" { # Do work that is necessary after a scroll}Find nodes
Use these functions to find interface nodes:
root()returns the root node for the current screen.find(...)returns the first matching node ornull.findAll(...)returns a list of matching nodes. The list can be empty.
Use node.find(...) or node.findAll(...) to search only inside a node.
Selectors
Selectors are named arguments. If you use multiple selectors, a node must match all of them.
| Selector | Match condition |
|---|---|
id= | View ID is equal to the value |
text= | Text is equal to the value |
desc= | Content description is equal to the value |
class= | Class name is equal to the value |
textContains= | Text contains the value, without a case check |
descContains= | Content description contains the value, without a case check |
clickable= | Clickable state is equal to the Boolean value |
scrollable= | Scrollable state is equal to the Boolean value |
selected= | Selected state is equal to the Boolean value |
checked= | Checked state is equal to the Boolean value |
feed = find(id="com.instagram.android:id/feed")likes = findAll(descContains="like")firstClickable = find(clickable=true)Match translated interface text
Displayed text can change with the app language. Use appString("resource_name") to get a translated string resource from the foreground app.
forYou = appString("guide_tab_title_for_you")if forYou != null and find(desc=forYou, selected=true) != null { # The translated For You tab is selected}appString(...) returns null if it cannot find the app or resource.
Node properties
Use node.property to read a node property.
| Group | Properties |
|---|---|
| Identity and content | id, text, desc, class, path |
| Position | x, y, left, top, right, bottom |
| Size | w, h, width, height |
| Center | cx, cy |
| State | clickable, scrollable, checked, selected, focused, enabled, visible |
| Children | childCount |
The path property gives a best-effort class and index path from the root. For example, it can return FrameLayout[0]/RecyclerView[1].
Node methods
| Method | Result |
|---|---|
node.child(i) | Child at index i, or null |
node.children() | List of direct children |
node.parent() | Parent node, or null |
node.find(...) | First matching node in the subtree, or null |
node.findAll(...) | List of matching nodes in the subtree |
node.hide(color=, touch=) | Overlay that covers the node bounds |
bar = find(id="com.app:id/toolbar")if bar != null { log("toolbar children=" + bar.childCount) for child in bar.children() { log(child.class + " @ " + child.x + "," + child.y) }}Draw overlays and do actions
Use these functions:
draw(x, y, w, h, color=, touch=, key=)draws an overlay rectangle.hide(node, color=, touch=)draws an overlay on the node bounds.back()does the Android Back action.home()does the Android Home action.log(...)writes a message with theUiHiderlog tag.
The draw() arguments have these functions:
| Argument | Function |
|---|---|
x, y | Set the upper-left position in pixels |
w, h | Set the width and height in pixels |
color= | Sets a hexadecimal color; the default follows the light or dark theme |
touch= | Blocks touches when true; permits touches through the overlay when false |
key= | Gives the overlay a stable identifier for smoother updates |
# Cover a fixed area and permit touches through itdraw(0, 0, screen.width, 120, color="#101010", touch=false)
# Leave a screen when its Reels tab is presentif find(id="com.app:id/reels_tab") != null { back()}Math and utility functions
The standard library has these functions:
- Math:
min(...),max(...),abs(x),floor(x),ceil(x),round(x),sqrt(x),pow(base, exp),clamp(value, low, high) - Length:
len(x)for a string or list - Conversion:
int(x)andstr(x) - Lists:
range(n)andrange(start, end) - App resources:
appString(name)
min(...) and max(...) accept multiple numbers or one list.
height = clamp(nav.top - top.bottom, 0, screen.height)Persistent storage
Each script has a private key-value store. Curbox keeps the values in memory and saves them to disk in the background.
| Function | Result |
|---|---|
save(key, value) | Saves a number, string, Boolean value, or list |
load(key) | Returns the saved value or null |
has(key) | Returns true if the key exists |
remove(key) | Removes the saved value |
Do not save a node. A node is valid only during the run that returned it. Save a node property instead.
Use storage to save a value that is expensive to calculate and does not change frequently:
forYou = load("forYouLabel")if forYou == null { forYou = appString("guide_tab_title_for_you") if forYou != null { save("forYouLabel", forYou) }}Use storage to keep a value between runs:
runs = load("runs")if runs == null { runs = 0}save("runs", runs + 1)Do not save screen positions that change when the user scrolls.
Script patterns
Cover an area between two nodes
top = find(id="com.app:id/header")bottom = find(id="com.app:id/footer")if top != null and bottom != null { height = bottom.top - top.bottom if height > 0 { draw(0, top.bottom, screen.width, height) }}Cover all matching nodes
for tile in findAll(id="com.app:id/recommend_card") { hide(tile)}Stop on an unrelated screen
if find(id="com.app:id/search_bar") == null { return}
# Add actions for the applicable screen hereDraw a transparent overlay
draw( 0, screen.height / 2, screen.width, screen.height / 2, color="#80000000", touch=false)Runtime limits
Curbox applies these limits to each run:
- Operation limit
- Time limit of approximately 40 milliseconds
- Node-visit limit
- Function recursion limit
- Overlay-count limit
Curbox stops the run if the script reaches a limit. The accessibility service continues to operate. Curbox writes a warning to the UiHider log tag.
Troubleshoot a script
- Check a
find()result fornullbefore you read its properties. - Check view IDs after an update to the target app.
- Remember that a range excludes its end value.
- Draw each required overlay during every run.
- Use
returnearly when the current screen does not apply. - Use a specific
find(id=...)call when an ID is available. - Keep loops small because Curbox runs a script frequently.
Use this command to read script log messages:
adb logcat -s UiHiderQuick reference
# Context: app screen.width screen.height event.type event.package event.text event.class# Find: root() find(<selectors>) findAll(<selectors>) node.find(...) node.findAll(...)# Selectors: id text desc class textContains descContains clickable scrollable selected checked# Node: id text desc class path x y left top right bottom w h width height cx cy childCount# State: clickable scrollable checked selected focused enabled visible# Methods: child(i) children() parent() find(...) findAll(...) hide(color=, touch=)# Actions: draw(x,y,w,h, color=, touch=, key=) hide(node, ...) back() home() log(...)# Utility: min max abs floor ceil round sqrt pow clamp len int str range appString# Storage: save(key, value) load(key) has(key) remove(key)# Control: if else if else while for x in <list|a..b> break continue# Functions: fn name(args) { ... return ... } return