# Features The full feature reference for Device Hub Pro. The [README](../README.md) has the short tour. ## First run on a Mac without the Android SDK When no `adb` can be found, the window shows a **Set up Android tools** card: - **Install Android Tools…** downloads Google's official command-line tools, platform-tools and emulator into `~/Library/Android/sdk` (Android Studio's default folder, so Studio can share them). It needs no administrator rights and downloads nothing until you accept Google's license. If the Mac has no Java runtime, it offers to download Eclipse Temurin into the app's own folder. - **Locate SDK…** points the app at an SDK folder you already have. - **I use Android Studio…** opens the Android Studio download page. When the tools are installed, the app offers to create your first emulator. ## Permissions macOS may ask for - **Local Network**, when you pair or use a wireless Android device. The app asks only when you need it, not at launch. - **Camera** and **Microphone**, only for the standard iPhone live view. The default native live view needs neither. - **Desktop, Documents and Downloads folders**, to save screenshots and recordings and to read APKs. ## Everything it does - **Devices and AVDs.** The sidebar lists running devices and installed AVDs (the Pixel Devices catalog built from the SDK's skins is under + ▸ Browse Catalog…). AVDs are created from any catalog skin; missing system images download in-app through sdkmanager (license acceptance and progress included), which needs the SDK's command-line tools and a Java runtime. A new AVD never overwrites an existing one: names are checked case-insensitively against the AVD folder and a free name is suggested. New Emulator… and the Pixel pages enforce each profile's minimum API (read from the SDK's bundled device definitions, ``; Pixel 10 family 36.1, Pixel 9 Pro Fold 35, Pixel Fold 34): images below it read "Requires API X+", Create stays disabled for them and the default is the newest image that fits (a download when none is installed); with no cmdline-tools or no entry for the profile everything is allowed. A new phone emulator defaults to a Google APIs image, where every Controls row works; Play Store images stay selectable (they cannot be rooted, so the rows that need root are not shown on them). Create (New Emulator…, the catalog, the setup card's "Create an Emulator…") never locks the window: the sheet closes at once and the system image download and the avdmanager create run in a background queue (`AvdCreationQueue`). The sidebar shows a "Creating" placeholder row per emulator (name, "Downloading Android 17… 34%", a thin progress bar, Cancel; a failure stays on the row with Retry) and a toolbar Downloads button opens a popover with every running download, the "Download More System Images…" sheet's included. One sdkmanager install runs at a time app-wide, so the next shows "Waiting" and starts by itself; creates run one at a time. The Pixel page's "Download & Create & Start" queues the same way and starts the AVD when it is ready, so leaving the page no longer cancels the download. Rows carry Rename…, Reset Content and Settings…, Remove… (the AVD moves to the macOS Trash) and Show in Finder, plus the All Devices / Simulators / Physical Devices filter. `ANDROID_AVD_HOME`, `ANDROID_USER_HOME`, `ANDROID_EMULATOR_HOME` and `ANDROID_SDK_HOME` are honoured. - **Hot-plug and reconnect.** The list follows `adb track-devices -l` (polling is only a fallback while the tracker is retried every 30 s). A pulled cable tears the mirror down and leaves a "disconnected" row; a returning device reconnects automatically with a capped backoff. Stop Mirror ends that episode, so a replugged device does not auto-resume. - **Push payloads and profiles on iOS simulators (drag and drop).** Xcode 27's Device Hub dropped Simulator.app's drag and drop of `.apns` payloads and certificates; Device Hub Pro has them back (drop on the stage or a sidebar row, or Send Files…, ⇧⌘U). A `.apns` file is sent with `simctl push `: the bundle id is the payload's `Simulator Target Bundle` key, and a file without one asks which installed app gets it (once per drop); several files go in order, and a payload that is not valid push JSON is explained by name. `.cer` / `.crt` / `.der` / `.pem` are trusted as root certificates (`simctl keychain add-root-cert`) after a confirmation. simctl has no command that installs a `.mobileconfig` profile, so a dropped profile gives up the certificates in it (root, PKCS#1 and PEM payloads, signed profiles too), which are trusted the same way after the same question; other payloads (Wi-Fi, VPN …) are left out and the drop overlay says so. An Android device answers a `.apns` drop with "Push notifications can't be sent to Android this way", a physical iPhone with "Push payloads and profiles can't be sent to a physical iPhone with public tools"; neither runs a command. - **Push library, saved deep links and Launch with Options.** Device ▸ Push Notification… has built-in templates (simple alert, title / subtitle / body, badge, sound, silent `content-available`, rich `mutable-content` with a category) and a library of named payloads (name, bundle id, payload; save, update, rename, duplicate, delete) kept in `~/Library/Application Support/DeviceHubPro/push-library.json`. The editor turns smart quotes and dashes off (pasted curly quotes are straightened), the payload is checked as JSON before it is sent and the problem is shown under the editor, and Device ▸ Resend Last Push repeats the last push (`last-push.json`). Open URL… (the simulator's and Android's) has a saved-links menu beside the recents: named links, grouped by app (the scheme is suggested), added, edited, duplicated and deleted in Manage Saved Links… (`deep-links.json`). An app's menu in a simulator's Apps list has Launch with Options…: arguments (one per line), environment variables (`KEY=value` per line, validated, passed as `SIMCTL_CHILD_`), wait for debugger (`simctl launch --wait-for-debugger`) and terminate the running instance first (`--terminate-running-process`); the last options are remembered per bundle id (`launch-options.json`). Streaming the app's stdout / stderr into the log pane is not offered: the simulator log pane reads the unified log, not a launch's console. - **Emulator lifecycle.** Emulators started from Device Hub Pro Quick Boot from the AVD's snapshot, like Android Studio; they cold-boot only after an automatic display repair or when a powered-off emulator is powered on. Shut Down (Device ▸ Shut Down, ⌘.) asks the emulator to shut down through its console so the snapshot is saved (this can take about a minute; the status line counts the seconds). Emulators started elsewhere are never force-killed by Shut Down. Emulators launched by the app use token-authenticated gRPC. - **Mirroring.** Emulators stream over the emulator's gRPC API. On emulator **37.2.3 or newer** frames come through shared memory (MMAP) by default; older emulators, and any MMAP failure, fall back to raw frames automatically (`DHP_DISABLE_MMAP=1` forces raw frames). MMAP frame files live in a private folder of the per-user temporary directory and are removed when the stream ends. Settings ▸ Emulator offers "Update the emulator to …" (through sdkmanager, with no emulator running) while the installed one is older than 37.2.3 and a newer one is available, because raw frames cost far more memory and CPU than MMAP. Settings ▸ Android system images lists the installed images with their size; Remove… uninstalls one through sdkmanager after saying what it frees and which emulators are built on it (they stop starting until it is downloaded again); an emulator running on it blocks the removal. Physical devices mirror through scrcpy's server protocol over adb (MediaCodec H.264 → VideoToolbox → Metal, zero-copy); mirroring wakes the phone's screen, and slow wireless-adb links are tolerated. The Metal view draws only when a new frame arrives. - **Input.** Emulators get gRPC touch including multi-touch; a two-finger trackpad scroll becomes one touch stroke that starts outside Android's gesture zones and only after the touch slop, so short nudges never tap. Physical devices use scrcpy's control socket: multi-touch, keys, text, real wheel scrolling, and right-click as Back (which also wakes the screen); `adb shell input` is the fallback. Text with non-ASCII characters (Turkish letters, emoji) is pasted through the device clipboard: on emulators the clipboard is restored afterwards, on phones each paste is acknowledged before the next. Control-key combinations and function keys are not typed as text. - **Port forwarding and a device shell (Android).** Device ▸ Port Forwarding… lists the device's `adb forward` and `adb reverse` rules with Add and Remove (every call names the device's serial). Device ▸ Shell… runs `adb -s shell ` one command at a time (not a terminal: nothing typed reaches a running command), with ↑/↓ history, Cancel and an output cap of 2 MB (oldest lines dropped). - **Wireless pairing.** File ▸ Pair Nearby Device… runs Android 11+'s pairing-code handshake. The connect port can stay empty: adb discovers it over mDNS. A pair whose connect fails keeps the pairing and offers Connect with a corrected port. - **Controls.** Battery level/charging, battery saver, airplane mode, Wi-Fi, Bluetooth, mobile data and Data Saver; location spoofing (the one Location menu below); volume; rotation (the fold strip under a foldable emulator handles posture and hinge; telephony, sensors, emulator pause and the fingerprint touch are in the Device menu, the fingerprint touch in the panel's Biometrics group too; a simulator's Settings panel is Device Hub's three cards followed by collapsed groups); appearance, text size, reduce motion, TalkBack, colour filters, contrast and developer toggles. The device settings are in the Device menu as well, on every platform: Accessibility, Appearance, Location, Sound, Language (with 24-hour time), Time Zone and Clean Status Bar (a simulator adds Face ID, push, permissions, Open URL and, as in Device Hub, Battery: the status bar's battery at 100, 75, 50 or 25 %), so each has a menu path and a key where one exists. An item the device has no hardware for is left out, read from the device itself (`pm list features`): no fingerprint touch on a Wear OS watch, no telephony, Shake or Split Screen on a TV. Rows show what the device actually does: Force RTL applies at once on Android 8+ (the language list is pushed again, as Developer options does) and shows "Restart pending" if that push did not take; Force RTL before Android 8 reads "Applies after the device restarts" or "Restart pending"; Battery saver is disabled while a charger is connected (with Turn Charging Off on emulators); a reading the device did not give shows as unknown instead of Off. Emulator-only sections are hidden for physical devices. On resizable AVDs (`hw.resizable.configs` in `config.ini`) the toolbar's Resize mode button and Device ▸ Enter Resize Mode offer the emulator's display presets (the New Emulator menus do not offer the Resizable profile). - **Foldable simulators (iPhone Duo, Xcode 27.1).** The stage's pill folds it, as Xcode's Device Hub does: Rotate shares a capsule with Closed, Partially Open and Open (Device Hub's own glyphs, read from the user's Xcode), and Laptop and Tent (half open on its side) sit beside them; the current posture shows in the accent colour, and the glyphs turn with the device as Device Hub's do. Device Hub's hinge slider (84 pt for 180°) is always at the capsule's right end, where Device Hub shows it only while Option is held; holding Option makes Rotate, which turns right, turn left. A 3D button after Rotate shows and hides the turnable 3D model, as View ▸ Show 3D Model does (the button is lit while it is on and is offered while the device frame is shown; the menu item stays). Where the stage is too narrow for all of it (the narrowest window with the inspector open, the compact window) the capsule gives up Laptop and Tent, then the slider, then the 3D button, then Save Replay (still in Record's right-click menu), and at the least width the pill is scaled to fit. Device ▸ Fold / Unfold does the same from the menu. As in Device Hub, a preset moves the hinge over about half a second (apps see the fold, not a jump) and Partially Open opens to Device Hub's angle, the device kept turned the way it is held; Device ▸ Orientation holds it portrait, upside down, landscape left or right, face up or face down, and Rotate turns it. The stage follows the lit panel: the outer one closed, the inner one open. The app remembers the fold while the simulator stays booted, so quitting and opening it again shows the device as it was left; a simulator something else opened (Device Hub, a test) is read off its two panels' screenshots, and the stage shows the lit one (a locked one dims its lock screen on the other panel: the brighter one is lit). When a fold leaves both panels dark for several seconds (a hung app kept the simulator's display change waiting), a banner over the pill offers to lock and wake the simulator, which lets it go. Where the selected Xcode ships Apple's 3D model of the device, the stage draws it as Device Hub does: seen from the front through a long lens, its body in neutral greys under one even light (its dark finish by default; View ▸ Device Color picks the light one), the black round each panel pure black, centred and sized as Device Hub's Zoom to Fit and Physical Size draw it (the stage's zoom and scale modes size it) and one size in every posture; the hinge folds on screen where the simulator's hinge is at each frame, and the device shows its outer panel square to the camera up to 55° (43° closing) and its inner one, held as a book, from there, as Device Hub turns it. Off a preset (the hinge slider) it faces the panel the simulator lights, at any angle, and swings to the other face when that changes: the simulator lights a panel by how the hinge moved, not only by where it stopped, so the app reads which one is lit after every fold, and at once when the panel it shows stays black for 0.3 s. The panel going dark keeps its last picture until the hinge stops (blurred in the turnable view); Laptop and Tent turn, tip and fold in one move, the lit panel facing the camera all the way (a laptop lies on its flat half, the camera half up as its lid); the lit panel shows the live simulator's own colours to its rounded edges and takes clicks, drags and, with Keyboard Capture, typing; its volume and side buttons move out under the pointer and in on a click (held as long as the click is), as a device frame's keys do. Zoom In, Zoom Out, Zoom to Fit and the scale modes scale the model itself (animated, never rebuilt, so it does not blank), and a zoomed model moves with ⌥⌘-drag or a scroll. View ▸ Show 3D Model lets the mouse turn it (a drag anywhere off the panel; a pinch or a scroll zooms, a double click faces it again, and View ▸ Turn Around shows its back), the hinge's angle beside it while it folds. The model is read from the user's Xcode at runtime, never copied (`DHP_DISABLE_DEVICE_MODEL=1` turns it off, and the device shows in its 2D frame). - **Language & time.** A Controls group for emulators and phones; a row is hidden when the device lacks its mechanism. Language is a searchable picker over the device's own language list (Android 8+), with suggested languages and Restore, which puts back the list from before the first change. A bundled `app_process` helper dex pushes the list the way Settings does (lists, `-u-` regional preferences, a new primary language); on Android 16 QPR2 (API 36.1) and newer, `cmd locale set-device-locale` takes a single listed language that keeps the primary language, and is the fallback when the helper fails. The helper's source and reproducible build are in `Sources/DeviceHubProKit/Controls/LocaleHelper/` and `Scripts/build-locale-helper.sh`. Date & time and Time zone (Android 9+) turn their automatic switch off by itself: Time zone's list starts with Automatic, which turns it back on, and Date & time shows a small Reset to automatic button while the time is set by hand. On a phone, a clock change asks first. 24-hour time is locale default, 12-hour or 24-hour. Each write is read back from where Android applies it. - **Network and app conditions.** Network conditions (emulators only): speed (the emulator's GSM, HSCSD, GPRS, UMTS, EDGE, HSDPA, LTE and EVDO rates, or Full), connection latency (GPRS/UMTS, EDGE/HSCSD and GSM presets, or a custom range), metered mobile data and Reset conditions (a caption under Speed names the data path being shaped; for offline testing use Network ▸ Airplane mode, Wi-Fi and Mobile data). Speed and latency are applied inside the device with `tc netem`, in both directions, on whichever interface carries the traffic (Wi-Fi or mobile data; the shaping follows the data path when it moves). The emulator console's own `network speed` throttles nothing and its `network delay` holds only new connection setups, so Device Hub Pro does not use them. `tc` needs root: Device Hub Pro runs `adb root` when you pick a condition (it restarts adbd; logcat reconnects) and `adb unroot` when conditions reset, unless the device was root already. Only a debuggable build allows that (a Google APIs image, not Play Store, `ro.debuggable=1`), so on Play Store images, user builds and physical phones the Speed and Connection latency rows are not shown. The latency is the added round trip, half in each direction. Disconnecting, quitting or Shut Down puts back what Device Hub Pro changed. App conditions (any device) act on a target app: Simulate low memory (Android 6+: one Send button that sends RUNNING_CRITICAL to an app in the foreground and COMPLETE to one in the background, gated like the activity manager; a refusal shows its reason) and Kill process (only while the app is in the background), confirmed by the pid and, on Android 11+, by Android's exit record. - **Clean status bar.** One switch, a plain row at the bottom of the Controls panel (Android 6+ emulators and phones; a simulator has the same switch above Reset to Defaults, and a Device menu item), that freezes the status bar for store screenshots with SystemUI's demo mode, the mechanism behind Developer options ▸ System UI demo mode: 9:41, full Wi-Fi and signal, battery 100 % not charging, notification icons hidden where Android supports it. Off ends it. Apps still see the real time, battery and network. `am broadcast` answers the same whether SystemUI took a command or not, so each write is read back from SystemUI: the switch from `dumpsys DemoModeController` (Android 12+; before that it shows the key Device Hub Pro wrote) and the battery from `BatteryController` (Android 13+). From Android 14 the demo mobile icon follows the real connection, so it is hidden; where SystemUI lists no demo handler for notification icons (the API 37 emulator), they stay. A phone maker's status bar may ignore demo mode. Turning it off, disconnecting, Shut Down and quit end Device Hub Pro's demo mode, realign SystemUI's battery and put `sysui_demo_allowed` and `sysui_tuner_demo_on` back. A put-back that could not reach the device is kept (per AVD, or per serial for a phone) and runs on its next session, also after a relaunch. Demo mode someone else turned on is left on at disconnect. - **Colour filters.** Controls ▸ Accessibility has Color Filter (Device Hub's None, Grayscale, Red/Green (Protanopia), Green/Red (Deuteranopia) and Blue/Yellow (Tritanopia)), on emulators and phones with Android 7+. It writes Android's Color correction settings for the user on screen (`accessibility_display_daltonizer` and its `_enabled` switch; an inversion set elsewhere still shows in the captions). None turns only the switch off and keeps the mode, as Settings does; the Intensity setting is never written. Each write is read back from SurfaceFlinger's colour matrix for the first enabled physical display (`dumpsys SurfaceFlinger --comp-displays`, Android 13+), and from the settings on Android 7–12L. A caption says when another colour transform (Night Light, Extra dim) is on too, or when the screen is off. A Developer options ▸ Simulate color space simulation reads "Simulated", and a mode Settings does not offer reads "Mode N". Screenshots leave the filter out and the emulator draws it wrongly, so check the look on a phone's own screen. Nothing is put back on disconnect: these are persistent accessibility settings. - **Link URL.** One row, a plain row at the bottom of the Controls panel (any Android device, a simulator and a physical iPhone alike), above Clean status bar: a URL field with the saved-links bookmark menu, the recent-links menu (the last 10 links opened from this Mac, with Clear Recents) and an Open button. On Android the link is opened as an `ACTION_VIEW` intent with `am start -W`, always with `CATEGORY_BROWSABLE` the way a browser click sends it and never aimed at one app, so the system resolves it like a real tap. It is sent exactly as typed: Device Hub Pro quotes it for the device shell and escapes non-ASCII bytes so they arrive unchanged (up to 30,000 bytes after escaping, 3,700 before Android 7). From Android 7 the URL caption previews the app that will open it and how many can (`cmd package resolve-activity` and `query-activities`); when no app opens it, the caption names an activity whose filter lacks `CATEGORY_DEFAULT`. Open reports what Android did: the activity it started with the launch kind and time, a reused screen or a task brought to the front (the app then gets the link only through `onNewIntent`, and only for a singleTop or singleTask activity), the chooser, no handler, or a refusal. With no answer within adb's 30 s bound it says so, and the link is not added to Recents. The row writes no settings, so nothing is put back. - **Location.** One menu, the same on Android emulators and iOS simulators: None, Device Hub's fourteen places, a Trips section (City Run, City Bicycle Ride, Freeway Drive) and Custom Location…, a sheet for a coordinate or a route between two points at a speed. A simulator runs Apple's own scenarios (`simctl location run`) and routes (`simctl location start`). An emulator has no scenarios, so Device Hub Pro plays routes of its own over the emulator's gRPC `setGps` at about 1 Hz (a run at 3 m/s, a bicycle ride at 6 m/s, a freeway drive at 30 m/s, repeating around Apple Park in Cupertino: these waypoints are Device Hub Pro's own, derived from the names and speeds, not Apple's), until you pick another location, None, or the device changes. Android cannot clear a fix, so None only stops a route and leaves the last position. A physical iPhone keeps its own subset (places and coordinates through devicectl). - **Clipboard.** Edit ▸ Send Clipboard, Get Clipboard and Use Shared Clipboard (automatic sync), for emulators, simulators and (over scrcpy's control channel) physical Android devices. - **Capture.** The pill's camera (and ⇧⌘S) saves a screenshot at once, like Device Hub's: in the Mac's screenshot folder (the Desktop by default) as "Screenshot at