fix: adapt hooks to HyperOS 3.3 (Android 17) - #1669
Open
mexusbg wants to merge 10 commits into
Open
Conversation
HyperOS 3.3's SystemUI is built with R8 constructor inlining, so several hooked classes have no <init> left in the dex (the constructor body is folded into the caller as new-instance plus iput). Constructor-based hooks therefore fail: loudly for explicit lookups, and silently for hookAllConstructors, which matches zero targets without logging anything. Rules are switched to equivalent non-constructor entry points, and every new branch is chosen by runtime capability detection with the old path tried first, so behaviour on older releases is unchanged. SystemUI: - HideVoWiFiIcon / MobileTypeSingle2Hook: OperatorConfig's constructor was inlined into MiuiOperatorCustomizedPolicy#getMiuiOperatorConfig; hook that method's result instead of constructors[0], which threw ArrayIndexOutOfBoundsException(length=0). - MobilePublicHookV: MiuiCellularIconVM has no constructor, so the second SIM's isVisible was never replaced and the dual-row signal was drawn once per mobile view (rendered twice). Handle it in MiuiMobileIconBinder#bind, and resolve subId via subscriptionId, which MobileIconInteractorImpl uses. - MobileTypeSingle2Hook: same bind-based fallback, unwrapping the MiuiMobileIconVMImpl wrapper to the inner MiuiCellularIconVM whose flows are still ReadonlyStateFlow. - StateFlowHelper: ReadonlyStateFlow.$$delegate_0 is declared StateFlow again, so the exact-type lookup for MutableStateFlow no longer matched. - JavaAdapter: alwaysCollectFlow(Flow, Consumer) now returns void, so the previous cast to a non-null Job threw NullPointerException and collection was never established. Call the static overload with applicationScope when the instance method is void so a cancellable Job is still available. - StatusBarIcon: RIGHT_BLOCK_LIST / CONTROL_CENTER_BLOCK_LIST are public static final and reject reflective writes. The lists are already mutated in place, so the write-back is no longer fatal. - PluginFactory: mComponentName was renamed to componentName. Reading only the old name threw, and the caller swallowed it into "Failed to create plugin context.", silently disabling every miui.systemui.plugin feature. NewPluginHelperKt now logs the throwable so this is diagnosable. Launcher: - WorkspacePadding: the DeviceConfig/DeviceConfigs choice was inverted with respect to the @Version(min = 600000000) mapping used by the other home rules, so recent launchers resolved a class without the padding getters. Pick whichever class actually declares them instead of trusting a version code, and handle the Init -> init rename. Graceful skips where the target no longer exists in the ROM: - RemoveSIMLockSuccessDialog: com.miui.simlock is absent from the global Security Center build. - DisableThemeAdNew: the built-in ad model was replaced by third-party SDKs. - AllowThirdTheme / DisableUploadAppListNew: DexKit anchor strings are gone; use optionalMember so a miss is skipped instead of throwing. - VariousThirdApps: android.os.Build's final fields cannot be written reflectively on Android 17, so report it as an unsupported-platform warning rather than an error. Also add Android 17 / HyperOS 3.3 to the supported version list.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 44 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
getOpenWithApps() builds the "Selected apps" list for both the clean share menu and clean open menu pickers. Three of its four queries pass the "HyperCeiler" bypass extra so CleanShareMenu / CleanOpenMenu skip filtering, but the ACTION_SEND query did not. CleanShareMenu strips every already selected package from ACTION_SEND resolution, so the picker asked the same question through its own hook. Any app whose only matching filter is ACTION_SEND vanished from the picker as soon as it was selected: it could no longer be reviewed or deselected, and the saved configuration looked lost even though the share menu was being cleaned correctly. Pass the bypass extra on the ACTION_SEND query as well. On a HyperOS 3.3 device with 23 selected packages the picker went from 136 to 144 entries, with the eight recovered apps correctly shown as checked.
Codacy reported 26 new issues on the HyperOS 3.3 compatibility changes. No behaviour changes; verified on a HyperOS 3.3 device after rebuilding (single dual-row signal stack, one 5G label with Wi-Fi off, VoLTE hidden, no hook failures in the LSPosed module log). CompareObjectsWithEquals (High): isFinalFieldRejection walked the cause chain and guarded against a self-referential cause with a reference comparison. Bound the walk to MAX_CAUSE_DEPTH instead, which removes the comparison and covers longer cycles too. LabeledExpression / NestedBlockDepth: guard clauses inside hook lambdas used labelled returns. Move each lambda body into a private function that takes the value it needs, so plain returns work: applyOnBind in MobilePublicHookV and MobileTypeSingle2Hook, bindConstructedResult for the constructAndBind intercept, and applyOperatorConfig now accepting a nullable config. applyToViewModel also accepts a nullable interactor so the constructor hook no longer needs a labelled return, and the two forEach loops that used return@forEach became for loops with continue. The repeated flow-proxy wiring in applyToViewModel moved into installDataSimProxy, which brings the nesting back under the limit. StringLiteralDuplication: extract the repeated reflection field and view id names into private constants. CommentOverPrivateFunction / CommentOverPrivateProperty: the explanations above private members stay, as line comments rather than KDoc. UndocumentedPublicFunction: document JavaAdapter.alwaysCollectFlow, including what a null return means. UnnecessaryFullyQualifiedName: use the imported BaseHook name.
PMD's FieldDeclarationsShouldBeAtStartOfClass flagged the constant added in the previous commit, which sat next to the method that uses it instead of at the top of the class.
settings.gradle.kts requires credentials for the GitHub Packages Maven repository on every build, but the only step that exports GIT_ACTOR and GIT_TOKEN is "Create Sign File", which is gated on pushes to main. Every pull request build therefore failed during settings evaluation with "Missing GitHub credentials", before compiling anything. The package is published to maven.pkg.github.com/ReChronoRain/HyperCeiler, which is this same repository, so the automatic GITHUB_TOKEN can read it once the job declares packages: read. That token is also available to pull requests from forks, where the GIT_TOKEN secret is withheld by design, so contributor builds work as well. Pushes to main are unchanged and keep using the GIT_ACTOR / GIT_TOKEN secrets exported by "Create Sign File".
The option had no effect on HyperOS 3. HideNavigationBar, which implements
it, is only registered in HomePhoneOld / HomePadOld (maxOSVersion = 2.0F)
and SystemUIV (maxSdk = 35), so on an OS 3 device neither the OS 3 launcher
class nor SystemUIB ever loads it.
Registering the existing hook for OS 3 is not the answer. It drives
NavStubView#mHideGestureLine, but that field feeds isImmersive,
getHotSpaceHeight, isNeedAdjustTouchArea and canPerformQuickSwitch, and
NavStubView#setHideGestureLine relayouts the gesture window. It does hide
the line, at the cost of resizing the gesture hot space, which breaks swipe
navigation and the long press that starts Circle to Search.
Hide the line by drawing instead. SystemUI paints it from
color/navigation_bar_home_handle_{light,dark}_color, so replacing both with
a fully transparent colour leaves the handle at its stock size while making
it invisible, and every touch region keeps the geometry the system computed.
dimen/navigation_handle_radius is deliberately left alone: forcing it to
zero also hides the line, but it feeds hit testing too, and a zero sized
handle stops Circle to Search.
Registered in SystemUIB only, so behaviour on older releases is unchanged,
and skipped when "customize gesture line" is on so a user's own thickness
and colour are not overwritten.
Verified on HyperOS 3.3 (Android 17): line hidden, swipe navigation and
Circle to Search both still working.
HyperOS 3 replaced the fragment based status bar with the HomeStatusBar view binder pipeline, so MiuiCollapsedStatusBarFragment no longer exists and the onViewCreated hook never fired. Fall back to MiuiPhoneStatusBarView#onFinishInflate, which is the same view the fragment used to expose as its root, and keep the fragment hook for builds that still have it.
- DisableThermal: ThermalManagerService moved to com.android.server.power.thermal and the dispatch method is now postEventListenerLocked; hook both names and keep the old class as a fallback. - AnimDurationRatio: DeviceLevelUtils moved from com.miui.home.launcher.common to com.miui.home.common.utils, and loadClass() aborted the whole hook when it was absent. - MoreNotificationSettings: the modal controller is exposed as IModalController, so look that up before the old ModalController. - AddAppManagerEntry: ManageApplications moved into the manageapplications subpackage; use the public ACTION_MANAGE_APPLICATIONS_SETTINGS intent instead of a class name.
…ckend Android 17 replaced PermissionManagerServiceImpl with the access based AppIdPermissionPolicy, and the method gained a leading MutateStateScope parameter, so the package state is now argument 1 rather than argument 0. Pick the class and the argument index together and keep the legacy pair for older releases.
miui_channel_notification_settings.xml calls the badge checkbox "setting_badge", so removeDefaultPrefs hid a key the filter never matched and the row stayed missing while importance and allow_keyguard came back. Match both keys so older builds keep working. Also drop the IModalController lookup added earlier: MiuiNotificationMenuRow no longer declares onClickInfoItem on HyperOS 3, so that handler is unreachable and the change had no effect.
mexusbg
force-pushed
the
fix/hyperos-3.3-android-17-compat
branch
from
August 6, 2026 22:04
b96100b to
2c51309
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On HyperOS 3.3 / Android 17 (SDK 37) the module was unusable: the app exited on launch, and once the version gate was lifted a number of SystemUI features were broken or rendered twice.
Most of it traces back to one change: HyperOS 3.3's SystemUI is built with R8 constructor inlining, so several hooked classes have no
<init>left in the dex — the constructor body is folded into its caller asnew-instance+iput:Constructor-based hooks therefore fail — loudly for explicit lookups, and silently for
hookAllConstructors, which matches zero targets without logging anything. That silent case is what made the duplicated dual-row signal hard to spot.Approach
Each rule moves to an equivalent non-constructor entry point. Every new branch is selected by runtime capability detection (
declaredConstructors.isNotEmpty(), field/method-name fallbacks, declared-type probes), always attempting the original path first, so behaviour on older releases is unchanged rather than gated on an SDK number.Fixed
SystemUI
HideVoWiFiIcon,MobileTypeSingle2HookOperatorConfigctor inlined intogetMiuiOperatorConfig;constructors[0]threwArrayIndexOutOfBoundsException(length=0)MobilePublicHookVMiuiCellularIconVMhas no ctor, so SIM 2'sisVisiblewas never replaced and the dual row was drawn once per mobile viewMiuiMobileIconBinder#bind; resolvesubIdviasubscriptionIdMobileTypeSingle2Hookbindpasses aMiuiMobileIconVMImplwrapper whose flows are coldChannelFlowTransformLatestStateFlowHelperReadonlyStateFlow.$$delegate_0is declaredStateFlowagain, so the exact-typeMutableStateFlowlookup missedStateFlowJavaAdapteralwaysCollectFlow(Flow, Consumer)now returnsvoid; casting to a non-null Job threw NPE and collection never startedapplicationScopewhen the instance method is voidStatusBarIconpublic static finaland reject reflective writes, abortinginit()PluginFactorymComponentNamerenamed tocomponentName; the throw was swallowed into"Failed to create plugin context.", silently disabling everymiui.systemui.pluginfeatureNewPluginHelperKtnow logs the throwableLauncher
WorkspacePaddingpicked its class withversionCode < 600000000 ? NEW : OLD, inverted with respect to the@Version(min = 600000000)mapping used by the other home rules, so recent launchers resolved a class that no longer declaresgetWorkspaceCellPadding*. It now picks whichever class actually declares the getters instead of trusting a version code, and handles theInit→initrename.Graceful skips
These targets are simply absent from the ROM, so they now skip with a debug line instead of throwing:
RemoveSIMLockSuccessDialog—com.miui.simlockis not in the global Security Center buildDisableThemeAdNew— the built-in ad model was replaced by third-party SDKsAllowThirdTheme,DisableUploadAppListNew— DexKit anchor strings are gone;optionalMemberskips a miss instead of throwingVariousThirdApps—android.os.Build's final fields cannot be written reflectively on Android 17 (clearingField.accessFlagsdoes not help either, as finality is enforced below that mirror), so it reports an unsupported-platform warning rather than an errorAlso adds Android 17 / HyperOS 3.3 to the supported version list as
SUPPORT_PARTIAL.Behaviour changes worth reviewing
StateFlowHelper.resolveDelegateFlowandJavaAdapter.startCollectreturnnullon lookup failure instead of throwing, so callers no-op rather than aborting.JavaAdapter.alwaysCollectFlowis consequentlyKotlinJob?.AllowThirdTheme/DisableUploadAppListNewno longer surface"Skip hook because initDexKit failed"; the hook is skipped just as before, only quietly.Testing
Verified on a Xiaomi
nezha_eea(2512BPNDAG), Android 17 / HyperOSOS3.0.332.0.XPAEUXM, SDK 37, LSPosed (KernelSU).MiuiOperatorCustomizedPolicydumpinghideVolte=true, hideVowifi=truefor every slotdumpsys telephony.registry(miuiLevel→ icon level) and confirmed to track each SIM independentlyHook Failed,Class not found,Skip hookor plugin-context errors remainassembleDebugpassesNot tested on OS 2.x / OS 3.0 hardware — the compatibility argument there rests on the old path being attempted first in every branch, which is worth a second pair of eyes.