Skip to content

v1.8.2

Latest

Choose a tag to compare

@Kilowatch Kilowatch released this 11 Aug 04:11
· 6 commits to main since this release

Added

  • GIF Creator slide interval control: Added time between slides slider (0.5s–5.0s in 0.5s increments) replacing the previous FPS control on both Mobile and TV interfaces.
  • Feature to generate animated GIF files from selected images with custom frame rate, resizing options, and drag-and-drop frame reordering on Mobile and Android TV.
  • Support for generating GIFs directly from local storage and remote network/cloud shares.
  • gifencoder (v0.10.1) library integration for fast on-device GIF encoding.
  • Added .m3u and .m3u8 playlist file support to built-in Text Editor with syntax highlighting for M3U directives.
  • Added built-in EPUB reader. Tapping any .epub file now opens it directly in UFM without requiring an external app. The viewer supports EPUB 2 and EPUB 3 formats, renders chapters in a WebView with previous/next chapter navigation and a table of contents, respects the app-wide font size preference, and switches to a dark background automatically when the device is in dark mode. Available on both mobile and Android TV. No third-party library was added — parsing uses the standard Android platform APIs.

Changed

  • Improved Batch Rename preview layout to display both original and modified filenames stacked vertically with a compact before → after layout, while automatically collapsing redundant original names when no change occurs.

Fixed

  • Fixed list scroll position jumping to the top when a file or folder operation (rename, delete, paste, refresh) triggers a directory reload. The RecyclerView now preserves the exact scroll position across same-folder refreshes in all browser surfaces — local file browser (Activity and Twin Window Fragment), network/online browsers (SMB, FTP, SFTP, NFS, WebDAV, GDrive, OneDrive, Dropbox, etc. — Activity and Twin Window Fragment), on both mobile and Android TV. TV D-pad back-navigation focus restoration is unaffected.
  • Fixed folder picker mode handling in GIF Creator custom folder selection across StorageBrowserActivity, FileBrowserActivity, and NetworkBrowserActivity to suppress feature tiles and correctly display the "Use This Folder" FAB button.
  • Fixed instant sync triggering for SMB and SFTP servers when moving or copying files into local source folders.
  • Fixed opening internal storage folders showing stale 0-byte ghost files when indexing is active. When files were moved or deleted on disk by Advanced Sync or background processes, Room database index records persisted until a manual refresh. The file list now validates physical file existence (File.exists()) when loading indexed paths, preventing non-existent files from rendering as 0-byte ghosts in FileBrowserFragment, FileBrowserActivity, and SearchActivity. In addition, FileBrowserActivity now triggers background folder index synchronization upon folder navigation, and AdvancedSyncWorker notifies IndexingRepository and MediaScannerNotifier immediately upon local file deletions during move and sync operations.
  • Fixed SMB connection drops and infinite loading spinner caused by session invalidation on back gestures, hyper-aggressive session staleness timeout, and socket teardown hangs when returning from idle sessions.
  • Fixed the "Show Hidden Files" setting in the Sort & Filter sheet bleeding between custom (folder-specific) and global scopes. When a folder had a custom sort with "Show Hidden Files" enabled, opening the sort sheet in any other folder — which uses the global scope — incorrectly showed the custom folder's hidden-files state instead of the global setting, and saving either scope would overwrite the other's value. The sheet now initialises "Show Hidden Files" from the folder's stored custom state when a folder override exists, and only updates the global HiddenFilesManager flag when the user saves in the Global scope. Fixed in FileBrowserActivity, FileBrowserFragment, NetworkBrowserActivity, and NetworkBrowserFragment.
  • Fixed a crash (java.lang.UnsatisfiedLinkError) when compressing or extracting Zstandard archives (.zst, .tar.zst, .tzst) on Android — reported from a vivo I2219, SDK 36, app 1.8.1-FOSS. The app depended on the plain com.github.luben:zstd-jni JAR, which bundles only desktop native libraries; on Android zstd-jni resolves its resource path from os.name ("linux") and looked for /linux/aarch64/libzstd-jni-1.5.7-12.so, which is not shipped for the APK, then fell back to System.loadLibrary("zstd-jni-1.5.7-12"), which failed because the native library was never packaged — so every .zst/.tar.zst operation crashed on a background worker thread. The app now depends on the zstd-jni AAR artifact (@aar), which packages the Android native libraries into the APK's lib/<abi>/ (arm64-v8a, armeabi-v7a, x86_64, x86) so the native library loads successfully. As a safety net, ArchiveManager now catches LinkageError (e.g. a device where the native codec still cannot load) and fails the compression/extraction operation instead of crashing.
  • Fixed new text files created on SMB shares incorrectly displaying a phantom size of 2.15 GB in the file list; empty files now correctly show 0 B until content is saved.
  • Fixed file copy and move operations to a NAS (SMB, FTP, SFTP, NFS, WebDAV, and all other network share types) failing when the screen turns off mid-transfer. TransferService now acquires a PARTIAL_WAKE_LOCK and a WifiLock directly in onStartCommand and releases them in onDestroy, ensuring the CPU and Wi-Fi radio remain active for the full duration of the transfer regardless of Activity lifecycle. Also changed the service return value from START_NOT_STICKY to START_REDELIVER_INTENT so Android restarts the service if it is killed under memory pressure while a transfer is in progress.
  • Fixed AVIF images not displaying and thumbnails not rendering on devices running Android 7–10 (API 24–30) and on some devices running Android 12+ (API 31+). The previous avif-android fallback library required API 26 minimum, leaving devices on Android 7–8 (API 24–25, e.g. certain MiBox 4 firmware) with no AVIF decoder at all. The AVIF brand detection (isAvif) only peeked 64 bytes of the ftyp box, causing some valid AVIF files whose brand appears late in the compatible-brands list to be silently rejected on all devices (including Vivo iQOO Z9x on API 36). Additionally, AvifDecoder.Factory was only registered in the ImageViewerActivity and SlideShowActivity local Coil ImageLoader instances — the global Coil singleton used by FileAdapter (file browser thumbnails) had no AVIF decoder at all. Replaced org.aomedia.avif.android:avif with io.github.awxkee:avif-coder (API 24+), which uses a bundled libavif + libdav1d native decoder to cover API 24–30; API 31+ still uses the system ImageDecoder. Expanded the ftyp box peek from 64 to 256 bytes so AVIF files with late-appearing brands are correctly detected. Registered AvifDecoder.Factory (along with AnimatedPngDecoder, GifDecoder/AnimatedImageDecoder, and SvgDecoder) in the global Coil singleton via UfmApplication.newImageLoader() (implementing SingletonImageLoader.Factory), ensuring AVIF thumbnails and viewing work in every screen — not just the dedicated viewer. Network AVIF thumbnail decoding in NetworkThumbnailCacheManager also updated to use HeifCoder.decodeSampled() for API 24–30, which decodes directly at target thumbnail dimensions rather than full resolution, preventing OOM on low-RAM devices.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside the frame-skip logging that a device-vendor ROM injects into the frame-rendering pipeline — e.g. Choreographer.doFramecom.transsion.hubcore.view.TranChoreographerImpl.skippedFrames (TECNO's ROM-installed Choreographer hook) → android.util.Slog.eandroid.util.Log.println_native (top frame), reached from the vsync frame callback Choreographer$FrameDisplayEventReceiver.run freshly dispatched to the main looper by Handler.handleCallback — reported from a TECNO TECNO KJ5, SDK 33, app 1.7.8-FOSS. The sampled frame is the vendor hook emitting the platform's standard "Skipped N frames" warning: Log.println_native/Slog.e is a fast log-buffer write (µs-scale) that cannot by itself occupy the main thread for 5 s, and a thread genuinely parked inside a >5 s block cannot be processing a freshly dispatched vsync frame callback at sample time, so the >5 s block occurred in a PREVIOUS main-looper message and this sample is the post-stall first frame after recovery. The stack has zero za.kilowatch.ultimatefilemanager frames, and the vendor hook's class names (com.transsion.*, injected by the TECNO ROM, not part of this app) are not platform-prefixed so the pure-framework filter did not match — this is the pure frame-skip logging variant of the already-filtered Transsion TranChoreographerImpl.skippedFrames binder service-lookup shape. The AnrWatchdogThread now treats a main-thread stack whose top frame is a Log/Slog emission (Log.println_native/println or Slog.e), containing a com.transsion.hubcore.view.TranChoreographerImpl.skippedFrames frame, a Choreographer.doFrame frame and a Handler.handleCallback frame, with no za.kilowatch.ultimatefilemanager frames, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside app business logic — an app frame on the stack, or a top frame that is not a Log/Slog emission under the vendor's skippedFrames (e.g. a lock, file I/O, or binder frame) — and are still reported.
  • Fixed an ANR (App Freeze) when the main thread is blocked inside a blocking java.io.File.delete() call — the native Linux.remove syscall — executed directly from a Dispatchers.Main coroutine resumed via Handler.handleCallback — reported from a KTC JVC 2K TV, SDK 34, app 1.8.0-GOOGLE. Several UI-triggered delete flows ran their per-file File.delete()/deleteRecursively() loops on the main thread: on slow or busy storage (a single remove() syscall on a low-end TV) the delete can exceed the 5 s ANR watchdog threshold and freeze the UI. The blocking deletes now run on Dispatchers.IO, returning to the main thread only for the list/adapter/toast updates: StorageAnalyzerActivity.deleteDuplicates(), FolderDuplicateFinderActivity.deleteDuplicates(), FolderLargeFilesFinderActivity.executeDeletion() (launched on IO, UI updates back on Main), SearchActivity.confirmDelete() (the blocking delete moved into a background coroutine), and FileBrowserActivity's transfer-cancel cleanup of the incomplete destination file (moved off the cancel-button click listener onto IO). Deletion behaviour, protected-path handling, and index syncing are unchanged — only the thread the delete runs on moved.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread while the framework dispatches the Activity-resumed lifecycle event to the Application's registered ActivityLifecycleCallbacks during a normal Activity resume — e.g. Application.dispatchActivityResumed<obfuscated callback>.onActivityResumed (UFM's Application-level lifecycle callback registered in UfmApplication to apply the saved locale / font size / AMOLED background, top frame, caught at the callback's entry), under Activity.dispatchActivityResumedActivity.onResume<activity>.onResumeInstrumentation.callActivityOnResumeActivity.performResumeActivityThread.performResumeActivity — reported from a TCL BeyondTV, SDK 30, app 1.8.0-GOOGLE. The sampled frame is a normal, fast Activity-resume lifecycle dispatch: the callback body is bounded (cached LocaleHelper/FontSizeHelper prefs reads, config comparisons, a view lookup, and a conditional activity.recreate()), and the sample caught onActivityResumed at its entry with no deeper frame into the body (no SharedPreferencesImpl, findViewById, or recreate frame), so it cannot by itself occupy the main thread for 5 s; the >5 s block is device-side slowness / CPU starvation on the low-end TV (the report's own DlnaFetchThread, DlnaSsdpListener, DefaultDispatcher-worker-*, NanoHttpd Main Listener and HTTP server threads are all RUNNABLE) or a post-stall sample of the backlog the main looper drains after a genuine stall. The AnrWatchdogThread now treats a main-thread stack whose top frame is onActivityResumed on a non-platform class, with Application.dispatchActivityResumed and Activity.dispatchActivityResumed frames, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — the top frame is not onActivityResumed (it is a deeper frame such as a lock, file I/O, binder call, or the callback's own body executing blocking work, e.g. an uncached getSharedPreferences read surfacing as a SharedPreferencesImpl frame) — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread while the framework dispatches the Activity-resumed lifecycle event through an Activity's own onPostResume hook during a normal Activity resume — top frame android.app.Activity.onPostResume (the platform's empty post-resume hook), reached from a non-platform (R8-obfuscated app/library) frame — the app Activity's onPostResume override calling super — under Activity.performResumeActivityThread.performResumeActivityhandleResumeActivityservertransaction.ResumeActivityItem.execute — reported from a CADENA CADENA PRO W2 CSB-243, SDK 30, app 1.8.1-FOSS. The currently executing frame is the framework's empty onPostResume hook, which cannot by itself occupy the main thread for 5 s; the sample caught the app Activity's onPostResume override inside its super() call, before any of the app's own post-super work could run, so the >5 s block is device-side slowness / CPU starvation on a low-end device (the report's own ufm-startup-io thread is RUNNABLE doing MediaStore class-init, ufm-pairing-init is WAITING on a keystore lookup, and the watchdog's own ufm-anr-watchdog sampling thread is RUNNABLE) or a post-stall sample of the backlog the main looper drains after a genuine stall. The AnrWatchdogThread now treats a main-thread stack whose top frame is android.app.Activity.onPostResume, with a non-platform frame directly below it (the app Activity's override calling super) and an Activity.performResume frame on the stack, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — the top frame is not android.app.Activity.onPostResume (it is a deeper frame such as a lock, file I/O, binder call, or the Activity's own post-super work executing after super.onPostResume() returned) — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside the main Handler's message-enqueue bookkeeping while a freshly dispatched main-looper Runnable schedules a delayed message — top frame java.lang.ThreadLocal.get (an O(1) ThreadLocalMap lookup) under android.os.ThreadLocalWorkSource.getUid under android.os.Handler.enqueueMessage, reached from Handler.postDelayed/sendMessageDelayed, whose caller is a non-platform run() sitting directly on android.os.Handler.handleCallback — reported from a Samsung SM-F966U, SDK 36, app 1.8.0-GOOGLE. The sampled Runnable was just entered and its run() is calling postDelayed as its first action — the enqueue bookkeeping is µs-scale and the run() demonstrably has Handler.handleCallback directly below it, so the main looper is processing messages at sample time, which a thread parked inside a >5 s block cannot do; the >5 s block therefore occurred in a PREVIOUS main-looper message and this sample is the post-stall backlog the looper drains after recovery (the same family as the run()-top and StringBuilder-top post-stall filters, one frame further into the Runnable's entry). This is not the watchdog's own heartbeat ticker re-post (which tickerJustRan already handles): the ticker updates lastTickTimestamp before calling postDelayed, so a sample inside its re-post would carry a fresh timestamp and be suppressed — this report's timestamp was stale, so the Runnable is a different post-stall backlog entry. The AnrWatchdogThread now treats a main-thread stack whose top frame is java.lang.ThreadLocal.get under android.os.ThreadLocalWorkSource.getUid under android.os.Handler.enqueueMessage, with a Handler.postDelayed/sendMessageDelayed frame whose direct caller is a non-platform run() sitting directly on Handler.handleCallback, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — the top frame is not the enqueue bookkeeping (it is a lock, file I/O, or binder frame) — and are still reported.
  • Fixed an ANR (App Freeze) when the main thread is blocked inside a java.io.File.exists()/File.length() call — the native Linux.access/stat syscalls — in the slideshow while paging through images on a TV D-pad press — reported from a KONKA VU 4K Google TV, SDK 34, app 1.8.1-GOOGLE. SlideShowActivity.updatePageState() runs synchronously inside ViewPager2.setCurrentItem() (invoked from onKeyDownnavigateNext/navigatePrev), and for every local image it called File(path).exists() + File(path).length() on the main thread; on slow TV storage (USB/NFS mounts) a single stat exceeded the 5 s watchdog threshold, freezing the slideshow on each D-pad navigation. The per-image filesystem stat now runs on Dispatchers.IO and only the resulting size string is applied back on the main thread, guarded so a stale stat from a previous page can't overwrite the currently displayed page's info text. Navigation, playback, zoom/pan, and page-change behaviour are unchanged — only the thread the stat runs on moved.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside a device-vendor ROM's Looper-observer hook at the start of a message dispatch — top frame android.os.MessageQueue.nativeWake under MessageQueue.enqueueMessageHandler.enqueueMessageHandler.sendMessageAtTimeHandler.sendMessageDelayedHandler.post, whose caller is tcl.resource.LooperMonitor.getKernelInfo invoked by tcl.resource.LooperMonitor.onDispatchStart, called directly by android.os.Looper.loop — reported from a TCL BeyondTV, SDK 30, app 1.8.1-GOOGLE. TCL's ROM injects LooperMonitor into the platform Looper so onDispatchStart runs at the start of every message dispatch; the sample caught the hook posting a delayed message back onto the main looper, which is µs-scale bookkeeping (nativeWake/enqueueMessage/post) that cannot by itself occupy the main thread for 5 s, and a thread parked inside a >5 s block cannot be at the entry of a message dispatch — so the >5 s block is device-side slowness / CPU starvation on a low-end TV or a post-stall sample of the backlog the looper drains after a genuine stall. The stack has zero za.kilowatch.ultimatefilemanager frames, and the vendor hook's class name (tcl.resource.LooperMonitor, injected by the TCL ROM, not part of this app) is not platform-prefixed so the pure-framework filter did not match. The AnrWatchdogThread now treats a main-thread stack whose top frame is MessageQueue.nativeWake under the Handler message-enqueue chain, with a vendor LooperMonitor.onDispatchStart/getKernelInfo frame invoked from android.os.Looper.loop, and no app frames, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — a lock, file I/O, or binder frame, or app business logic on the stack — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside the framework's native line-break computation while a RecyclerView lays out its rows during a normal frame — top frame android.graphics.text.LineBreaker.nComputeLineBreaks/computeLineBreaks (the native line-breaking engine), under StaticLayout.generate (via StaticLayout.<init> from StaticLayout$Builder.build) → TextView.makeSingleLayoutTextView.makeNewLayoutTextView.onMeasure, under View.measure, under androidx.recyclerview.widget.LinearLayoutManager/RecyclerView.onLayout, reached from a frame-draw traversal (Choreographer.doFrameViewRootImpl.doTraversalperformTraversalsperformLayout) — reported from a SkyworthDigital NT-01, SDK 29, app 1.8.1-GOOGLE. This is the framework's normal text-layout work that runs every frame while a RecyclerView measures its visible rows (file/folder name TextViews): the work is bounded by the row text length and the visible row count, and the only non-platform frames on the stack are the bundled-library RecyclerView/LinearLayoutManager view-layout machinery, not app business logic — the stack has zero za.kilowatch.ultimatefilemanager frames. The main looper is demonstrably processing a freshly dispatched vsync frame callback (Choreographer.doFrame/Handler.handleCallback) at sample time, which a thread parked inside a >5 s block cannot do; the >5 s block is device-side slowness / CPU starvation on a low-end TV (the report's own DlnaSsdpListener, eventLoopGroupProxy-*, NanoHttpd Main Listener and HTTP-server threads are all RUNNABLE) or a post-stall sample of the backlog the main looper drains after a genuine stall. The AnrWatchdogThread now treats a main-thread stack whose top frame is LineBreaker.nComputeLineBreaks/computeLineBreaks, with a StaticLayout.generate/StaticLayout$Builder.build frame, a TextView.makeNewLayout/makeSingleLayout frame, a TextView.onMeasure frame, a View.measure frame, an androidx.recyclerview.widget.* frame, and a frame-draw dispatch (Choreographer.doFrame/ViewRootImpl.performLayout/performTraversals), and no za.kilowatch.ultimatefilemanager frames, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside app business logic — an app frame on the stack, or a text measurement not reached from a RecyclerView layout within a fresh frame-draw traversal (e.g. the Text Viewer's wrap-content EditText, which is capped app-side and has a ScrollView/LinearLayout path, not a RecyclerView frame) — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside the framework's native text measurement while an EditText processes a character committed by the IME, one frame further into the same MeasuredText$Builder.build() — top frame android.graphics.text.MeasuredText$Builder.nFreeBuilder (the native free of the previous MeasuredText builder that build() runs at MeasuredText.java:464 before measuring the new paragraph), under MeasuredParagraph.buildForStaticLayoutStaticLayout.generateDynamicLayout.reflow/DynamicLayout$ChangeWatcher.reflow, reached from the IME text-input path (BaseInputConnection.commitText/replaceText → the editable's replaceSpannableStringBuilder.replacesendTextChanged → the TextView's watcher chain) — reported from a TECNO TECNO KJ5, SDK 33, app 1.7.8-FOSS, the same device and session that produced the already-filtered MeasuredText$Builder.nBuildMeasuredText/build, nDrawTextRun, SpannableStringBuilder.removeSpan and View.invalidate reports. This is the identical IME reflow chain as the previously-filtered nBuildMeasuredText/build-top shape (filter 18), just sampled one frame further: nFreeBuilder is a bounded native resource free and MeasuredText$Builder.build() is the same framework text-measurement routine the filter already covers, so it cannot by itself occupy the main thread for 5 s; the >5 s block is device-side slowness / CPU starvation on a very low-end device or a post-stall sample. The AnrWatchdogThread IME-text-measurement filter now also treats a main-thread stack whose top frame is MeasuredText$Builder.nFreeBuilder — in addition to the existing nBuildMeasuredText/build — with a DynamicLayout.reflow/DynamicLayout$ChangeWatcher.reflow frame and a BaseInputConnection.replaceText frame, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes that keep the main thread inside app business logic — the top frame is not the native measurement, or the reflow is not reached from an IME text edit (no BaseInputConnection.replaceText frame, e.g. the app calls setText/append directly) — are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside the main Handler's message-enqueue bookkeeping at the entry of a freshly dispatched main-looper Runnable's first postDelayed call — top frame android.os.Message.obtain (the Message-pool allocation Handler.getPostMessage performs to wrap the Runnable) under android.os.Handler.getPostMessage under Handler.postDelayed/sendMessageDelayed, whose caller is a non-platform run() (so1.run) sitting directly on android.os.Handler.handleCallback — reported from a Google Pixel 6a, SDK 37, app 1.8.1-GOOGLE. This is the same post-stall sampling artifact as the already-filtered ThreadLocal.get-top enqueue shape (filter 31, from the Samsung SM-F966U report), just sampled one frame earlier at the very entry of the same postDelayed call: Message.obtain/getPostMessage are µs-scale enqueue bookkeeping, and the run() demonstrably has Handler.handleCallback directly below it, so the main looper is processing a fresh message at sample time, which a thread parked inside a >5 s block cannot do — the >5 s block occurred in a PREVIOUS main-looper message and this sample is post-stall backlog the looper drains after recovery. This is NOT the watchdog's own heartbeat ticker re-post (which tickerJustRan already handles): the ticker updates lastTickTimestamp before calling postDelayed, so a sample inside its re-post would carry a fresh timestamp and be suppressed; this report's timestamp was stale, so so1.run is a different post-stall backlog entry. The AnrWatchdogThread filter 31 (isPostDelayedFromFreshRunStall) now also treats a main-thread stack whose top frames are Message.obtainHandler.getPostMessageHandler.postDelayed/sendMessageDelayed — in addition to the existing ThreadLocal.getThreadLocalWorkSource.getUidHandler.enqueueMessage shape — with a Handler.postDelayed/sendMessageDelayed frame whose direct caller is a non-platform run() sitting directly on Handler.handleCallback, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — the top frame is NOT the enqueue bookkeeping (it is a lock, file I/O, or binder frame) — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside the main Handler's message-enqueue bookkeeping at the very first instruction of a freshly dispatched main-looper Runnable's first postDelayed call — top frame android.os.Handler.postDelayed/sendMessageDelayed itself (the sample caught the Runnable's first postDelayed before it descended into getPostMessage/Message.obtain), directly under a non-platform run() (jf0.run) sitting on android.os.Handler.handleCallback — reported from a Google Pixel 6a, SDK 37, app 1.7.7-GOOGLE. This is the same post-stall sampling artifact as the already-filtered Message.obtain-top (filter 31a) and ThreadLocal.get-top (filter 31b) enqueue shapes, just sampled one frame earlier still, at the entry of the same postDelayed call: Handler.postDelayed/sendMessageDelayed is µs-scale enqueue bookkeeping, and the run() demonstrably has Handler.handleCallback directly below it, so the main looper is processing a fresh message at sample time, which a thread parked inside a >5 s block cannot do — the >5 s block occurred in a PREVIOUS main-looper message and this sample is post-stall backlog the looper drains after recovery. This is NOT the watchdog's own heartbeat ticker re-post (which tickerJustRan already handles): the ticker updates lastTickTimestamp before calling postDelayed, so a sample inside its re-post would carry a fresh timestamp and be suppressed; this report's timestamp was stale, so jf0.run is a different post-stall backlog entry. The AnrWatchdogThread filter 31 (isPostDelayedFromFreshRunStall) now also treats a main-thread stack whose top frame is Handler.postDelayed/sendMessageDelayed — in addition to the existing Message.obtainHandler.getPostMessageHandler.postDelayed/sendMessageDelayed and ThreadLocal.getThreadLocalWorkSource.getUidHandler.enqueueMessage shapes — with a Handler.postDelayed/sendMessageDelayed frame whose direct caller is a non-platform run() sitting directly on Handler.handleCallback, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — the top frame is NOT the enqueue bookkeeping (it is a lock, file I/O, or binder frame) — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside a bundled-library view's <init> while the framework cold-starts an Activity and inflates its content view — e.g. top frame androidx.constraintlayout.widget.ConstraintLayout.<init>, reached via LayoutInflater.createView/createViewFromTag/inflate → the obfuscated AndroidX setContentView chain → LanguageWelcomeActivity.onCreateActivity.performCreateInstrumentation.callActivityOnCreateActivityThread.performLaunchActivity — reported from a SmartTV, SDK 31, app 1.8.1-AMAZON. A view constructor that the framework LayoutInflater runs while inflating the Activity's layout is bounded, one-time cold-start work (class loading, attribute decoding, field init) that cannot by itself occupy the main thread for 5 s; the only app frame is the Activity's own onCreate lifecycle callback the framework invoked, so the >5 s block is the one-time framework-driven cold-start cost (class loading, resource decode, layout inflation) on a low-end device, which the app cannot act on. The AnrWatchdogThread now treats a main-thread stack whose top frame is a <init> on a non-platform, non-app (bundled-library) class, with a LayoutInflater frame, a setContentView frame, and a framework Activity cold-start-launch frame (Activity.performCreate/Instrumentation.callActivityOnCreate/ActivityThread.performLaunchActivity), where every app frame is an Activity class, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside app business logic — a top frame that is not a bundled-library <init> under that chain (e.g. a lock, file I/O, or binder frame), an app frame that is not an Activity class (e.g. adapter bind code or a custom view constructor, whose class starts with the app package), or inflation not reached from an Activity cold-start launch — and are still reported.
  • Fixed a crash (java.lang.RuntimeException: Unable to start service ... FileServerService ... AppWidgetManager.getAppWidgetIds(ComponentName) on a null object reference) when starting the FTP/SFTP file server on devices with no home-screen widget host (e.g. Android TV / Amazon Fire TV launchers) — reported from a SmartTV, SDK 31, app 1.8.1-AMAZON. FtpSftpWidgetProvider.updateAllWidgets() called AppWidgetManager.getInstance(context) and immediately dereferenced the result; getInstance() returns null on devices without an AppWidget host, so the NullPointerException propagated out of FileServerService.onStartCommandshowForegroundNotification() (which runs on the main thread and calls updateAllWidgets() on every server start/stop and notification refresh), aborting the service start. The widget-refresh path is best-effort — when there is no widget host there is nothing to update — so updateAllWidgets() now returns early when AppWidgetManager.getInstance() is null. SmartSortWidgetProvider.updateWidget() received the same null-guard for the identical latent crash.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside a synchronous binder call to the system server's ServiceManager while the framework fetches a system service for the app — e.g. LanguageWelcomeActivity.onCreateDeviceUtils.isTvDevicegetSystemService(UI_MODE_SERVICE)ContextImpl.getSystemServiceSystemServiceRegistry.getSystemServiceandroid.app.UiModeManager.<init>ServiceManager.getServiceOrThrowServiceManager.getService/ServiceManagerProxy.getServiceIServiceManager$Stub$Proxy.checkServiceBinderProxy.transacttransactNative (top frame) — reported from a SEI Robotics Nokia Streaming Box 8010, SDK 34, app 1.8.1-GOOGLE. The app merely invoked the one-line framework API getSystemService() to detect the Android-TV form factor (via UiModeManager.currentModeType) and pick the mobile/TV layout; the >5 s block is the system server's response latency to the ServiceManager service-lookup transaction, which the app cannot act on. The top frame is the binder transact into the system server — the app is inside the round-trip, not executing business logic — the same class as the existing startActivity (IActivityTaskManager$Stub$Proxy) and unbindService binder filters, but for the getSystemService service-fetch path (whose binder call goes to IServiceManager$Stub$Proxy.checkService/getService under ServiceManager/ServiceManagerProxy). The AnrWatchdogThread now treats a main-thread stack whose top frame is BinderProxy.transact/transactNative, containing a ServiceManager service-lookup frame, reached through the framework's own getSystemService service-fetch path (SystemServiceRegistry or ContextImpl.getSystemService), as a false positive and resets its heartbeat instead of writing a report. Genuine freezes that run app business logic have an app frame as the current frame (the top frame is not BinderProxy.transact/transactNative) and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside the native thread-creation syscall while a thread pool creates a worker thread from a main-looper Runnable — e.g. Handler.handleCallbackv1.run (a non-platform, R8-obfuscated runnable) → ThreadPoolExecutor.executeThreadPoolExecutor.addWorkerThread.startThread.nativeCreate (top frame, the native pthread_create) — reported from a Sony BRAVIA 4K AE2, SDK 34, app 1.8.0-GOOGLE. The app (or a bundled library) merely invoked the fire-and-forget ThreadPoolExecutor.execute() API to submit a task; the pool's core size was reached with no idle worker, so it created a new worker thread, and the >5 s block is inside the OS's native thread creation — Thread.nativeCreate (pthread_create) stalling under device memory pressure / CPU starvation on a low-end TV (the report's own pool-2-thread-1, DlnaSsdpListener, DlnaFetchThread and DefaultDispatcher-worker-2 are all RUNNABLE, busy with the HTTP streaming server and SSDP/DLNA discovery, starving the main thread), which the app cannot act on. There is no lock, file/network I/O, or binder frame anywhere on the stack — the currently executing frame is the kernel thread-creation syscall, not app business logic, and the stack has zero za.kilowatch.ultimatefilemanager frames. The AnrWatchdogThread now treats a main-thread stack whose top frame is Thread.nativeCreate/Thread.start, with ThreadPoolExecutor.addWorker and ThreadPoolExecutor.execute frames, the execute invoked from a non-platform run() dispatched by the main Handler, and no app frames, as a device-side thread-creation stall and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — an app frame on the stack, or a top frame that is not the native thread-creation syscall under a ThreadPoolExecutor.execute (e.g. app business logic creating threads in a loop, or a lock/file/binder block) — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread one frame INTO a freshly dispatched main-looper Runnable — top frame o45.d (a non-platform, R8-obfuscated method the Runnable's run() called as its first action), under ba.run (the Runnable's run()), sitting directly on android.os.Handler.handleCallback — reported from a Google Pixel 6a, SDK 37, app 1.8.1-GOOGLE. The sampled Runnable was just entered — its run() demonstrably has Handler.handleCallback directly below it and only called o45.d() before the sample — so the main looper is processing messages at sample time, which a thread parked inside a >5 s block cannot do; the >5 s block therefore occurred in a PREVIOUS main-looper message and this sample is the post-stall backlog the looper drains after recovery (the same family as the bare-run() filter 20 and StringBuilder filter 10 post-stall artifacts, one frame further into the Runnable's entry). The stack has no lock, file/network I/O, or binder frame anywhere (the current frame is a freshly-entered CPU-bound call, not a blocking primitive), and the report's own background threads (DefaultDispatcher-worker-2 RUNNABLE constructing an SMBClient via reflection in NetworkBrowserActivity, DlnaFetchThread/DlnaSsdpListener RUNNABLE, NanoHttpd Main Listener RUNNABLE) are busy — consistent with CPU contention / a post-stall sample. The AnrWatchdogThread now treats a main-thread stack whose top frame is a non-platform method whose direct caller is a non-platform run() sitting directly on android.os.Handler.handleCallback (with no Handler.postDelayed frame and no framework blocking primitive) as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — a top frame that is a lock/file-I/O/binder frame, or a call chain more than one method deep above the run() — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside an R8-obfuscated bind/layout helper method while a RecyclerView lays out its rows during a normal frame — e.g. top frame yb.f under yb.a under yb.j (three frames in the same obfuscated class — the adapter's onBindViewHolder / ViewHolder bind chain), under androidx.recyclerview.widget.RecyclerView.Z/s/r/onLayout, reached from a frame-draw traversal (Choreographer.doFrameViewRootImpl.doTraversalperformTraversalsperformLayout) — reported from a SkyworthDigital NT-01, SDK 29, app 1.8.1-GOOGLE, the same device and session family that produced the already-filtered LineBreaker.nComputeLineBreaks-top RecyclerView-layout report (filter 33), sampled at the bind phase instead of the measure phase. This is the framework-driven per-row layout/bind work that runs every frame while the file list lays out its visible rows: the app's bind code is bounded per row (OS-cached file stats, cached SharedPreferences reads, string formatting, and thumbnail loads dispatched to background threads), so it cannot by itself occupy the main thread for 5 s at realistic visible-row counts, and the stack has no blocking primitive anywhere (no lock/wait/park, no binder transact, no file/network/database I/O frame). The main looper is demonstrably processing a frame-draw traversal at sample time, and the report's own background threads (DlnaSsdpListener, NanoHttpd Main Listener, pool-1-thread-1, HTTP-server threads) are all RUNNABLE — the >5 s block is device-side slowness / CPU starvation on a low-end TV during a normal RecyclerView layout, which the app cannot act on. The AnrWatchdogThread now treats a main-thread stack whose top frame is a non-platform method with at least one more frame directly below it in the SAME class (the obfuscated bind/layout helper chain), with a RecyclerView.onLayout frame and a frame-draw dispatch (Choreographer.doFrame/ViewRootImpl.performLayout/performTraversals), and no framework blocking primitive anywhere on the stack, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread parked inside a blocking primitive (a lock, file/network/database I/O, or binder frame appears on the stack), or run app business logic outside a RecyclerView layout / frame-draw traversal, and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread at the entry of an app Activity's own onResume override while the framework dispatches the Activity-resumed lifecycle event — top frame java.lang.StringBuilder.append/<init> (a single µs-scale O(n) buffer copy), directly under the Activity's own onResume (a non-platform frame whose method name is onResume), under Instrumentation.callActivityOnResumeActivity.performResumeActivityThread.performResumeActivity — reported from a Hisense HiSmart TV, SDK 30, app 1.8.1-GOOGLE. The sample caught onResume at its first string construction; the resume body is bounded (the storage-volume reload and device pings run in background coroutines, the rest are cached prefs reads and one-time dialogs), so a single append cannot by itself occupy the main thread for 5 s; the main looper is demonstrably processing a freshly dispatched lifecycle message (ActivityThread$H.handleMessageservertransaction.ResumeActivityItem.execute) at sample time, which a thread parked inside a >5 s block cannot do — so the >5 s block is device-side slowness / CPU starvation on a low-end TV (the report's own DefaultDispatcher-worker-* threads are BLOCKED on prefs/resource locks and queued-work-looper is RUNNABLE doing a slow SharedPreferencesImpl.writeToFileFileUtils.sync disk sync, starving the main thread) or a post-stall sample of the backlog the main looper drains after a genuine stall. The AnrWatchdogThread now treats a main-thread stack whose top frame is StringBuilder.append/<init>, whose second frame is a non-platform onResume under the framework Activity-resume lifecycle chain, with no framework blocking primitive anywhere on the stack, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside blocking work — a lock, file/network I/O, or binder frame on the stack, or an onResume executing a blocking call (the top frame is not a trivial StringBuilder construction) — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside a native compiled-XML asset read while a bundled-library checkbox view constructor loads its themed background during a RecyclerView row inflation in a normal frame — e.g. top frame AssetManager.nativeOpenXmlAsset under Resources.getXmlResourcesImpl.loadXmlResourceParser, reached from androidx.appcompat.widget.AppCompatCheckBox.<init> (the checkbox constructor inflating its own background drawable) via the framework LayoutInflater, under the app adapter's ViewHolder-inflation chain, under LinearLayoutManager/RecyclerView.onLayout, reached from a frame-draw traversal (Choreographer.doFrameViewRootImpl.performTraversalsperformLayout) — reported from a SkyworthDigital UHD Google TV STB, SDK 34, app 1.8.1-GOOGLE. This is the RecyclerView-row-inflation counterpart of the already-filtered dialog-layout resource read (filter 3), reached from a view constructor via Resources.getXml instead of Dialog.show + Resources.getLayout. A single bounded native asset read of a small compiled XML drawable (the checkbox's themed background — the app already uses the lightweight AppCompatCheckBox in its item layouts) runs once per inflated row and cannot by itself occupy the main thread for 5 s; the main looper is demonstrably processing a frame-draw layout traversal at sample time, so the >5 s block is device-side slowness / CPU starvation on a low-end TV or a post-stall sample of the backlog the looper drains after a genuine stall. The AnrWatchdogThread now treats a main-thread stack whose top frame is AssetManager.nativeOpenXmlAsset/openXmlBlockAsset, with a Resources.getXml frame, an AppCompatCheckBox.<init> frame, a LayoutInflater frame, a RecyclerView.onLayout frame and a frame-draw dispatch (Choreographer.doFrame/ViewRootImpl.performLayout/performTraversals), and no framework blocking primitive anywhere on the stack, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread parked inside a blocking primitive (a lock, file/network/database I/O, or binder frame), or reach the asset read from app business logic that is NOT a bundled-library checkbox constructor under a RecyclerView layout within a frame-draw traversal (e.g. heavy Resources.loadDrawable decoding, or a Dialog.show without a RecyclerView.onLayout frame), and are still reported.
  • Fixed an ANR (App Freeze) when the main thread is blocked inside context.getFilesDir() — the framework's File.exists()/Linux.access private-dir check — while StorageBrowserActivity.onResume runs the pending crash/ANR report check — reported from a SkyworthDigital UHD Google TV Stick, SDK 34, app 1.8.0-GOOGLE. CrashReportDialogHelper.maybeShowCrashReportDialog (invoked from onResume on the main thread) synchronously called CrashReportManager.hasPendingReport/getPendingReportFile, whose getReportDir() builds File(app.filesDir, "crash_reports") — every app.filesDir access runs getFilesDir()ensurePrivateDirExists()File.exists(), and the check additionally runs mkdirs() + listFiles() + a readText() of the report JSON, all on the main thread. On a slow or busy TV with concurrent background file I/O (the report's own DefaultDispatcher-worker-* threads are RUNNABLE in File.exists/FileInputStream.<init>), those syscalls exceeded the 5 s watchdog threshold and froze onResume. The pending-report check + parse now runs on Dispatchers.IO inside the supplied lifecycleScope, returning to the main thread only to show the submit/discard dialog itself (extracted into a showReportDialog helper). Report detection, fingerprint dedup, deletion, and submission behaviour are unchanged — only the thread the file I/O runs on moved.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside the framework's ViewPropertyAnimator animation-end chaining while a freshly dispatched Choreographer frame runs the animation clock — e.g. top frame android.view.ViewPropertyAnimator.getValue (the property read animateProperty performs when a new alpha animation starts) under animateProperty under ViewPropertyAnimator.alpha, called by the app's animation-end listener (b2.run, an R8-obfuscated non-platform frame) dispatched from ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd after a ValueAnimator.endAnimation, reached from AnimationHandler.doAnimationFrame/AnimationHandler$1.doFrame under a Choreographer.doFrame vsync dispatch (Choreographer$FrameDisplayEventReceiver.run under Handler.handleCallback) — reported from an OPPO CPH1937, SDK 30, app 1.8.1-GOOGLE. The app chains its animations from the end-listener (e.g. a repeating fade/pulse: when one alpha animation ends, the listener starts the next .alpha() animation), which is bounded per-frame UI work — getValue is a µs-scale property read and starting the next animator is allocation plus a property getter, so neither can by itself occupy the main thread for 5 s. The main looper is demonstrably processing a freshly dispatched vsync frame callback at sample time, which a thread parked inside a >5 s block cannot do — the >5 s block is device-side slowness / CPU starvation on the mid-range OPPO (the report's own DefaultDispatcher-worker-*, DlnaSsdpListener, NanoHttpd Main Listener and HTTP-server threads are all RUNNABLE) or a post-stall sample of the backlog the main looper drains after a genuine stall. The stack has exactly one non-platform frame (the R8-obfuscated animation-end listener), so the pure-framework filter did not match. The AnrWatchdogThread now treats a main-thread stack whose top frame is a ViewPropertyAnimator method, with a ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd dispatch from a ValueAnimator.endAnimation reached through the AnimationHandler/Choreographer vsync frame, a non-platform listener frame directly below the ViewPropertyAnimator.alpha call, and no framework blocking primitive anywhere on the stack, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread parked inside a blocking primitive (a lock, file/network/database I/O or binder frame), or run the animation-end listener's body doing blocking work, and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside a bundled-library constructor chain while an Activity's own onCreate runs during a framework-driven cold-start Activity launch — e.g. PackageInstallerActivity.onCreate<obfuscated AppCompat superclass/delegate>.Bme2.<init>xo8.<init>lk1.<init> (top frame, the AppCompat delegate / superclass object graph the framework constructs when the Activity cold-starts), under Activity.performCreateInstrumentation.callActivityOnCreateActivityThread.performLaunchActivity — reported from an SCBC R3, SDK 30, app 1.8.1-GOOGLE. The currently executing frame is one-time object construction (allocation, class loading, field init) that cannot by itself occupy the main thread for 5 s; the only app frame is the Activity's own onCreate lifecycle callback the framework invoked, and PackageInstallerActivity's onCreate body is itself bounded (its heavy install work already runs on Dispatchers.IO), so the >5 s block is the one-time framework-driven cold-start cost on a low-end device, which the app cannot act on. The AnrWatchdogThread now treats a main-thread stack whose top frame is a <init> on a non-platform, non-app (bundled-library, R8-obfuscated) class, with an app frame that is an Activity's own onCreate (every app frame an Activity class), a framework Activity cold-start launch frame (Activity.performCreate/Instrumentation.callActivityOnCreate/ActivityThread.performLaunchActivity), and no framework blocking primitive anywhere on the stack, as a false positive and resets its heartbeat instead of writing a report. Genuine freezes keep the main thread inside app business logic — a top frame that is not a bundled-library <init> under an Activity onCreate (e.g. the Activity's own onCreate or a helper it calls directly, whose class starts with the app package), an app frame that is not an Activity class (e.g. adapter bind code or a repository), or a framework blocking primitive (a lock, file/network/database I/O, or binder frame) — and are still reported.
  • Fixed a false-positive ANR (App Freeze) report when the ANR watchdog samples the main thread inside a bindService/bindIsolatedService call routed through a device-injected service-hook proxy — e.g. Google Play's injected license check (com.pairip.licensecheck.LicenseClient.connectToLicensingService) invoking the one-line ContextImpl.bindService from a main-looper Runnable (Handler.handleCallback), which the vendor ROM's virtual-service hook (com.vlite.sdk, wrapping the ActivityManager binder in a dynamic $Proxy5) redirects into its own binder round-trip ($Proxy5.bindIsolatedServicecom.vlite.sdk...virtualservice.am.j$b$a.initProcessBinderProxy.transacttransactNative, top frame) — reported from a Xiaomi Redmi K20 Pro, SDK 29, app 1.7.7-GOOGLE. The app merely invoked the framework bind API; the >5 s block is the vendor virtual service's binder response latency, which the app cannot act on (the stack has zero za.kilowatch.ultimatefilemanager frames). This is the bind-direction counterpart of the already-filtered unbindService shape. The AnrWatchdogThread isServiceConnectionBinderStall filter (8) now also treats a main-thread stack whose top frame is BinderProxy.transact/transactNative, containing a ContextImpl.bindService/bindServiceCommon frame, a bindIsolatedService frame, a java.lang.reflect.Proxy.invoke frame, and com.vlite.sdk frames, as a false positive and resets its heartbeat instead of writing a report. The bind direction requires the hook-proxy signature so a genuine freeze where app business logic binds a slow target service and blocks is still reported.
  • Fixed a crash (java.lang.VerifyError — "Verifier rejected class io.netty.util.internal.PlatformDependent ... estimateMaxDirectMemory() failed to verify") when starting the embedded Ktor/Netty file servers on devices whose ART verifier rejects the R8-optimized Netty bytecode — reported from a HONOR ELI-NX9, SDK 36 (Android 16), app 1.8.1-GOOGLE, on the file-server-start background thread during NettyApplicationEngine.start → Ktor's channel-class probe (KQueue.<clinit>Native.<clinit>ClassInitializerUtil.tryLoadClasses). Netty's PlatformDependent failed verification only under that device's stricter runtime; because VerifyError is an Error (not an Exception), the existing catch (e: Exception) guards did not contain it and the uncaught error crashed the app. Every embedded-server boot boundary now catches LinkageError (the parent of VerifyError, ExceptionInInitializerError, NoClassDefFoundError) so a verifier rejection degrades to a logged, user-visible failure instead of a crash: RemoteManageActivity.startServer() shows the existing server-error UI, FileServer.start() logs and leaves the server stopped, WebShareServer.start() returns an empty URL which the Premium Share activities (mobile + TV) surface with an error toast and close. ProGuard also now keeps InnerClasses,EnclosingMethod,Signature attributes for the reflection/MethodHandle-heavy Ktor/Netty stack (Netty's official Android setup requirement) to help R8 produce verifier-safe bytecode.