From bd0fb9d3ab4065f7fc8d2b5da0031be289b9629e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Andr=C3=A9s=20Trujillo?= <34223334+jatezzz@users.noreply.github.com> Date: Fri, 28 Nov 2025 09:32:34 -0500 Subject: [PATCH 01/42] ADFA-2164 | Prevent crash when loading invalid font files (#650) * fix: prevent crash when loading invalid font files Add validation and UI warnings for empty or corrupt font files. * refactor(layouteditor): Change threads handling, add magic constants and Log exception * refactor(layouteditor): Address bot comments, first valid then copy file, initialize executor and shutdown in onDestroyView * fix(fonts): validate before copy and avoid broken UI entries * refactor(strings): add a descriptive error message --- .../fragments/resources/FontFragment.java | 88 +++++++++++++++---- .../codeonthego/layouteditor/utils/Utils.java | 52 +++++++++++ layouteditor/src/main/res/values/strings.xml | 5 +- resources/src/main/res/values/strings.xml | 1 + 4 files changed, 127 insertions(+), 19 deletions(-) diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/FontFragment.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/FontFragment.java index bfeef57f76..d51a2d805c 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/FontFragment.java +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/fragments/resources/FontFragment.java @@ -1,5 +1,7 @@ package org.appdevforall.codeonthego.layouteditor.fragments.resources; +import static org.appdevforall.codeonthego.layouteditor.utils.Utils.isValidFontFile; + import android.content.Context; import android.net.Uri; import android.os.Bundle; @@ -33,6 +35,8 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class FontFragment extends Fragment { @@ -40,6 +44,7 @@ public class FontFragment extends Fragment { private FontResourceAdapter adapter; private ProjectFile project; private List fontList = new ArrayList<>(); + private ExecutorService executor; @Override public android.view.View onCreateView( @@ -52,26 +57,54 @@ public android.view.View onCreateView( @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - loadFonts(); - RecyclerView mRecyclerView = binding.recyclerView; + executor = Executors.newSingleThreadExecutor(); adapter = new FontResourceAdapter(fontList); + RecyclerView mRecyclerView = binding.recyclerView; mRecyclerView.setAdapter(adapter); mRecyclerView.setLayoutManager( new LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)); + loadFonts(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + executor.shutdownNow(); + binding = null; } private void loadFonts() { - File[] files = project.getFonts(); + executor.execute(() -> { + File[] files = project.getFonts(); - if (files == null) { - ToastUtils.showShort("Null"); - } else { + if (files == null) { + requireActivity().runOnUiThread(() -> ToastUtils.showShort(getString(R.string.msg_error_load_failed))); + return; + } + + List temp = new ArrayList<>(); for (File file : files) { String name = file.getName(); - // name = name.substring(0, name.lastIndexOf(".")); - fontList.add(new FontItem(name, file.getPath())); + + if (!isValidFontFile(file)) { + requireActivity().runOnUiThread(() -> + ToastUtils.showLong(getString(R.string.msg_font_load_invalid, name)) + ); + continue; + } + temp.add(new FontItem(name, file.getPath())); } - } + + requireActivity().runOnUiThread(() -> { + fontList.clear(); + fontList.addAll(temp); + if (adapter != null) adapter.notifyDataSetChanged(); + }); + }); + } + + private void postToast(String msg) { + requireActivity().runOnUiThread(() -> ToastUtils.showLong(msg)); } public void addFont(final Uri uri) { @@ -98,15 +131,34 @@ public void addFont(final Uri uri) { builder.setPositiveButton( R.string.add, (di, which) -> { - String fontPath = project.getFontPath(); - - String toPath = fontPath + editTextName.getText().toString() + extension; - FileUtil.copyFile(uri, toPath, getContext()); - - String name = editTextName.getText().toString(); - var fontItem = new FontItem(name + extension, toPath); - fontList.add(fontItem); - adapter.notifyItemInserted(fontList.indexOf(fontItem)); + final String finalName = editTextName.getText().toString().trim(); + final String finalToPath = project.getFontPath() + finalName + extension; + final String finalFileName = finalName + extension; + + executor.execute(() -> { + String filePath = FileUtil.convertUriToFilePath(getContext(), uri); + File original = new File(filePath); + + if (!isValidFontFile(original)) { + postToast(getString(R.string.msg_font_add_invalid)); + return; + } + + boolean copySucceeded = FileUtil.copyFile(uri, finalToPath, getContext()); + + if (!copySucceeded) { + File failedFile = new File(finalToPath); + if (failedFile.exists()) failedFile.delete(); + postToast(getString(R.string.msg_font_copy_failed)); + return; + } + + requireActivity().runOnUiThread(() -> { + FontItem item = new FontItem(finalFileName + extension, finalToPath); + fontList.add(item); + adapter.notifyItemInserted(fontList.size() - 1); + }); + }); }); final AlertDialog dialog = builder.create(); diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/utils/Utils.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/utils/Utils.java index 44f6f4f8b2..c9ca58e2bf 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/utils/Utils.java +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/utils/Utils.java @@ -26,10 +26,13 @@ import org.appdevforall.codeonthego.vectormaster.VectorMasterDrawable; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.Date; import java.util.Locale; import java.util.concurrent.Callable; @@ -155,6 +158,55 @@ public static int getScreenWidth() { return displayMetrics.widthPixels; } + /** + * Basic validation of a TTF/OTF file. + * Checks: + * 1. File exists and is not empty + * 2. Valid TTF/OTF header (scaler type) + * 3. Directory table has a reasonable number of entries + */ + public static boolean isValidFontFile(File file) { + final int MAX_FONT_TABLES = 100; + final int FONT_HEADER_SIZE = 12; + final int TABLE_DIRECTORY_ENTRY_SIZE = 16; + + if (!file.exists() || file.length() < FONT_HEADER_SIZE) { + return false; + } + + try (FileInputStream stream = new FileInputStream(file)) { + byte[] header = new byte[FONT_HEADER_SIZE]; + if (stream.read(header) != FONT_HEADER_SIZE) { + return false; + } + + ByteBuffer buf = ByteBuffer.wrap(header).order(ByteOrder.BIG_ENDIAN); + + // --- Validate scaler type --- + int scalerType = buf.getInt(); + boolean validScaler = + scalerType == 0x00010000 || // TTF + scalerType == 0x4F54544F; // OTTO + + if (!validScaler) return false; + + // --- Number of tables --- + int numTables = buf.getShort() & 0xFFFF; + if (numTables == 0 || numTables > MAX_FONT_TABLES) { + return false; + } + + int directorySize = numTables * TABLE_DIRECTORY_ENTRY_SIZE; + byte[] tableDir = new byte[directorySize]; + + return stream.read(tableDir) == directorySize; + + } catch (IOException e) { + Log.e("Utils", "Error validating font file: " + file.getName(), e); + return false; + } + } + public static VectorMasterDrawable getVectorDrawableAsync(Context context, Uri uri) { Callable callable = new Callable() { diff --git a/layouteditor/src/main/res/values/strings.xml b/layouteditor/src/main/res/values/strings.xml index b778d2bcc2..a664a55b93 100644 --- a/layouteditor/src/main/res/values/strings.xml +++ b/layouteditor/src/main/res/values/strings.xml @@ -122,7 +122,10 @@ Save changes and exit Discard changes and exit Cancel and stay in Layout Editor - + Failed to load font %1$s. File is empty or invalid. + Failed to add font. The file is empty or corrupt. + Failed to load file list. + Selected file is not an Android XML layout file Failed to load palette diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index 681656cd4d..cdb77deb8e 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -681,6 +681,7 @@ Selected file is not a directory Project directory does not exist Failed to read project cache + Could not save the font file. Please try again. A file save operation is already in progress! Install Development Tools \nTo install the tools needed to build Android projects, tap the button at the bottom right of From 4694561abfa361a1025e8707396dde6e107f8389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Andr=C3=A9s=20Trujillo?= <34223334+jatezzz@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:32:29 -0500 Subject: [PATCH 02/42] Implement On-Demand Loading for Llama Module from Assets (#491) * feat: Integrate llama.cpp as a new module This commit introduces a new `:llama` Android library module that integrates the `llama.cpp` library for local large language model inference. The module provides a `LLamaAndroid` Kotlin class, which acts as a singleton wrapper around the native `llama.cpp` functions via JNI. This enables loading models, managing contexts, tokenizing text, and running inference. Key changes: - **Module Creation:** Added a new `:llama` library module with its own Gradle build files (`build.gradle.kts`), ProGuard rules, and Android Manifest. - **Native Integration:** - Included a `CMakeLists.txt` to build `llama.cpp` from a local source directory. - Added `llama-android.cpp` which contains the JNI bridge between Kotlin and the native C++ functions. - Implemented native functions for model/context/batch/sampler management, tokenization, inference (`completion_init`, `completion_loop`), and benchmarking. - Configured `llama_log_set` to redirect native logs to Android's Logcat. - **Kotlin Wrapper (`LLamaAndroid.kt`):** - Created a singleton `LLamaAndroid` class to manage the lifecycle of the native llama.cpp objects. - Exposed high-level functions like `load()`, `unload()`, `send()`, `tokenize()`, and `bench()`. - Manages native state on a dedicated single-threaded coroutine dispatcher to ensure thread safety. - **Build Configuration:** - Added `:llama` as a dependency to the main `:app` module and included it in `settings.gradle.kts`. - Configured CMake arguments in `llama/build.gradle.kts` to customize the `llama.cpp` build (e.g., disabling CURL). - **Bug Fixes & Improvements:** - Increased the `n_batch` size to 2048 in `LLamaAndroid.kt` to prevent failures with larger contexts. - Corrected the implementation of the `tokenize` JNI function to use the `common_tokenize` helper, ensuring consistency. - Added a repetition penalty sampler to the default sampler chain in the C++ code to improve text generation quality. * refactor: Use relative path for llama.cpp submodule This commit updates the `CMakeLists.txt` file to use a relative path instead of a hardcoded absolute path for the `llama.cpp` dependency. This change makes the build process portable and independent of the local user's directory structure. The `llama.cpp` source is now expected to be located in `subprojects/llama.cpp/`. * refactor: Use relative path for llama.cpp submodule This commit updates the `CMakeLists.txt` file to use a relative path instead of a hardcoded absolute path for the `llama.cpp` dependency. This change makes the build process portable and independent of the local user's directory structure. The `llama.cpp` source is now expected to be located in `subprojects/llama.cpp/`. * Unnecessary files removed * Unnecessary files removed * Unnecessary files removed * Unnecessary files removed * Unnecessary files removed * Unnecessary files removed * Unnecessary files removed * COmments * COmments * working * refactor * refactor(agent): Improve Local LLM Repository and agentic loop This commit refactors the local LLM implementation by moving the inference logic into a dedicated `LlmInferenceEngine` object within the `repository` package. Key changes: - **New `LlmInferenceEngine`:** A new singleton object in the `agent.repository` package now manages the `LLamaAndroid` instance, including model loading, unloading, and inference. This replaces the deleted `LlmInterfaceEngine` from the `app` package. - **Improved Agentic Loop:** The agentic loop in `LocalLlmRepositoryImpl` is now more robust. It correctly formats history for the model, adds the model's raw response to the history for better context, and continues the loop when tool call parsing fails. - **Refined System Prompt:** The system prompt is clearer, instructing the model to use `[TOOL_CALL]` markers and providing a better example. Tool definitions are also formatted more cleanly. - **`generateCode` Implementation:** Implemented the `generateCode` function to allow code generation with the local LLM. - **Lenient JSON Parsing:** The JSON parser is now configured to be lenient to better handle malformed JSON output from the model. * feat(agent): Add support for local LLM backend This change introduces a switchable backend system for the AI Agent, allowing users to choose between the existing Gemini API and a new local LLM backend. The `ChatViewModel` has been refactored to initialize the appropriate AI repository (`AgenticRunner` for Gemini or `LocalLlmRepositoryImpl` for local models) on-demand when a message is sent. This removes the previous one-time initialization logic and allows the backend to be switched dynamically. The `AiSettingsFragment` is updated to: - Allow users to select their preferred AI backend (Gemini or Local LLM). - Provide a file picker for users to select a local model file when the Local LLM backend is chosen. - The UI now dynamically shows settings relevant to the selected backend. * binding * binding * loading * Model loaded successfully * info from settings * feat(ai): Synchronize model loading state between UI and engine This commit refactors the local AI model loading logic to ensure the UI state (`AiSettingsViewModel`) is always synchronized with the underlying inference engine (`LlmInferenceEngine`). Key changes: - `LlmInferenceEngine` now tracks the loaded model's name and status. - The `getFileNameFromUri` helper function is moved into `LlmInferenceEngine` to keep state management self-contained. - `AiSettingsViewModel` now checks the engine's status on startup (`checkInitialSavedModel`) to correctly reflect an already-loaded model in the UI, preventing the need for the user to reload it. - Model loading in the ViewModel is simplified to directly sync its state from the engine after a load attempt. * feat(agent): Add backend status indicator to chat screen This commit introduces a UI element on the chat screen to display the currently active AI backend (e.g., Gemini or a local model's filename). This replaces the previous "Agent Mode" dropdown menu. Key changes: - Adds a backend status view in `fragment_chat.xml` which includes an icon and text to show the current AI backend. - Removes the now-unused "Agent Mode" dropdown menu. - In `ChatViewModel`, logic is added to format and expose the backend display text via `LiveData`. - The view model now updates this status display continuously when the fragment is resumed, in addition to showing a one-time system message when the backend is changed. * feat(agent): Implement cooperative cancellation for agent tasks This change introduces a robust mechanism to stop ongoing AI agent tasks, including local LLM inference and agentic workflows. Key changes: - A `stop()` method has been added to the `GeminiRepository` interface and implemented across all relevant repositories (`LocalLlmRepositoryImpl`, `AgenticRunner`, `SwitchableGeminiRepository`). - The native `LLamaAndroid` class now includes a `stop()` method that sets an atomic flag, allowing the inference loop to terminate cooperatively. - `AgenticRunner` now uses a cancellable `CoroutineScope` to manage its lifecycle, allowing its execution loop to be interrupted. - `ChatViewModel`'s `stopAgentResponse()` method now calls both `agentRepository.stop()` and cancels the coroutine job to ensure a fast and clean shutdown of the agent task. - Error handling in `ChatViewModel` is improved to manage cancellations gracefully and reset the agent's state. * feat(agent): Implement cooperative cancellation for agent tasks This change introduces a robust mechanism to stop ongoing AI agent tasks, including local LLM inference and agentic workflows. Key changes: - A `stop()` method has been added to the `GeminiRepository` interface and implemented across all relevant repositories (`LocalLlmRepositoryImpl`, `AgenticRunner`, `SwitchableGeminiRepository`). - The native `LLamaAndroid` class now includes a `stop()` method that sets an atomic flag, allowing the inference loop to terminate cooperatively. - `AgenticRunner` now uses a cancellable `CoroutineScope` to manage its lifecycle, allowing its execution loop to be interrupted. - `ChatViewModel`'s `stopAgentResponse()` method now calls both `agentRepository.stop()` and cancels the coroutine job to ensure a fast and clean shutdown of the agent task. - Error handling in `ChatViewModel` is improved to manage cancellations gracefully and reset the agent's state. * Refactor(agent): Improve local LLM agentic loop and tool handling This commit introduces several key improvements to the local Large Language Model (LLM) agent: - **Refactors Tool Execution**: The `Tool.execute` method's argument type is changed from `Map` to `Map` for better type safety and consistency with JSON parsing. - **Improves Agentic Loop**: The agentic loop in `LocalLlmRepositoryImpl` is significantly enhanced. It now builds a more structured prompt history, manages conversation state, and uses stop strings to better control model output and detect the end of tool calls. - **Enhances Tool Parsing**: A new `parseToolCall` function replaces the previous regex-based approach. It now uses `JSONObject` for more robust parsing of `` blocks from the model's output. - **Removes Unused Code**: The `SwitchableGeminiRepository` class, which was responsible for switching between different AI backends, has been entirely removed as it is no longer in use. Cleaned up comments in `AgenticRunner`. - **Adds Stop String to Inference**: The `LlmInferenceEngine.runInference` method now accepts a list of stop strings, allowing more fine-grained control over the generation process. * feat(agent): Show intermediate progress for local LLM and Gemini This change introduces an `onProgressUpdate` callback to show intermediate steps from the AI agent in the chat UI. The `GeminiRepository` and `AgenticRunner` interfaces now include the optional `onProgressUpdate` callback. In `ChatViewModel`, this callback is implemented for both `AgenticRunner` and `LocalLlmRepositoryImpl` to add progress messages directly to the current chat session. The `LocalLlmRepositoryImpl` has been refactored to leverage this new callback. It now sends back "thought" processes, tool calls, and tool results as system messages during its agentic loop. This removes the need for the repository to manage its own internal UI message list, simplifying its logic and providing real-time updates to the user. * feat(agent): Add timers to track agent operation duration This commit introduces timers to measure and display the total and step-by-step duration of AI agent operations. - A new `onStateUpdate` callback is added to the agent repositories (`AgenticRunner`, `LocalLlmRepositoryImpl`) to listen for state changes. - The `ChatViewModel` now starts a timer when an agent operation begins and stops it upon completion. - A `resetStepTimer()` function is called whenever the agent enters a new `Processing` state, allowing for tracking the duration of individual steps. * feat(agent): Add timers to track agent operation duration This commit introduces timers to measure and display the total and step-by-step duration of AI agent operations. - A new `onStateUpdate` callback is added to the agent repositories (`AgenticRunner`, `LocalLlmRepositoryImpl`) to listen for state changes. - The `ChatViewModel` now starts a timer when an agent operation begins and stops it upon completion. - A `resetStepTimer()` function is called whenever the agent enters a new `Processing` state, allowing for tracking the duration of individual steps. * feat(chat): Replace placeholder message with new message Instead of updating the "thinking..." placeholder message with the agent's response, remove the placeholder and add the agent's response as a new message. This ensures the new message appears at the bottom of the chat, improving the user experience. * feat(agent): Add collapsible system messages This commit introduces a separate, collapsible view type for system and tool messages in the agent chat. Key changes: - Created a new `list_item_chat_system_message.xml` layout for system messages, featuring an expandable/collapsible header. - Added new icons (`ic_robot`, `ic_expand_more`) for the system message view. - Modified `ChatAdapter` to support multiple view types (`VIEW_TYPE_DEFAULT` and `VIEW_TYPE_SYSTEM`). - Implemented `SystemMessageViewHolder` to handle the binding and expand/collapse logic for system messages. - Added an `isExpanded` state to the `ChatMessage` data class to manage the visibility of system message content. - Updated `DiffCallback` to ensure smooth animations when the `isExpanded` state changes. * feat(agent): Add collapsible system messages This commit introduces a separate, collapsible view type for system and tool messages in the agent chat. Key changes: - Created a new `list_item_chat_system_message.xml` layout for system messages, featuring an expandable/collapsible header. - Added new icons (`ic_robot`, `ic_expand_more`) for the system message view. - Modified `ChatAdapter` to support multiple view types (`VIEW_TYPE_DEFAULT` and `VIEW_TYPE_SYSTEM`). - Implemented `SystemMessageViewHolder` to handle the binding and expand/collapse logic for system messages. - Added an `isExpanded` state to the `ChatMessage` data class to manage the visibility of system message content. - Updated `DiffCallback` to ensure smooth animations when the `isExpanded` state changes. * feat: Show preview for collapsed system logs Adds a single-line preview for collapsed system log messages in the chat. This provides users with context about the log's content without needing to expand it. When collapsed, the system message header now displays "Log: " followed by a cleaned-up, single-line version of the markdown content. When expanded, it reverts to the simple "System Log" title. * feat(logging): Replace Android Log with SLF4J Replaces the Android `Log` utility with SLF4J for structured and more robust logging across the `llama` and `app` modules. This change introduces the SLF4J dependency and refactors the logging calls in `LLamaAndroid` and `LlmInferenceEngine` to use the new framework. This improves logging consistency and removes unnecessary comments and KDoc. * feat: Bridge native llama.cpp logs to slf4j Introduces a JNI bridge to forward logs from the native `llama.cpp` layer to the Kotlin/Java side, utilizing the slf4j logging framework. This change replaces the direct `__android_log_print` calls with a more robust logging mechanism. It correctly handles JNI thread attachment and detachment to prevent crashes, ensuring that native logs are properly surfaced within the Android application's logging system. * feat: Add specific C++ to Kotlin info logging Introduces a new C++ function `log_info_to_kt` to send formatted `INFO` level logs directly to the Kotlin bridge. This is used to replace a general `LOGi` call for more specific logging of token caching details. The `LLamaAndroid.kt` file is also cleaned up by removing a redundant comment. * Refactor: Move `getFileNameFromUri` to Uri extension and remove unused code This commit refactors the codebase by: - Moving the duplicated `getFileNameFromUri` function into a `getFileName` extension function on the `Uri` class within `common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt`. - Updating all call sites to use the new extension function. - Removing the unused `TOOL` enum value from `ChatMessage.Sender`. - Simplifying the tool call parsing logic in `LocalLlmRepositoryImpl` by using Kotlinx Serialization instead of manual JSON parsing. * Refactor: Move `getFileNameFromUri` to Uri extension and remove unused code This commit refactors the codebase by: - Moving the duplicated `getFileNameFromUri` function into a `getFileName` extension function on the `Uri` class within `common/src/main/java/com/itsaky/androidide/utils/UriExtensions.kt`. - Updating all call sites to use the new extension function. - Removing the unused `TOOL` enum value from `ChatMessage.Sender`. - Simplifying the tool call parsing logic in `LocalLlmRepositoryImpl` by using Kotlinx Serialization instead of manual JSON parsing. * docs: Add KDoc to buildPromptWithHistory Add detailed KDoc to the `buildPromptWithHistory` function explaining its purpose and implementation details. The documentation clarifies that the function currently uses the Llama 3.x chat template and provides guidance on how to adapt it for other model families like Mistral, Gemma, or Phi-3 by modifying the template structure. * feat: Improve chat message expansion state management Refactor the expansion state handling for chat messages. Instead of storing the `isExpanded` state within the `ChatMessage` data model, the state is now managed entirely within the `ChatAdapter`. This change prevents unnecessary recompositions of the entire chat list when a single message's expansion state changes. It introduces a more efficient mechanism using a set of expanded message IDs and `notifyItemChanged` with a specific payload, resulting in smoother UI updates. * feat(agent): Add localized strings for AI model loading status Introduces new string resources to provide clearer, localized feedback to the user in the AI settings screen regarding the state of the local model. This change replaces hardcoded status messages for model loading (idle, loading, loaded, error) with references to the new `strings.xml` entries, improving maintainability and preparing for future internationalization. * feat(agent): Add localized strings for AI model loading status Introduces new string resources to provide clearer, localized feedback to the user in the AI settings screen regarding the state of the local model. This change replaces hardcoded status messages for model loading (idle, loading, loaded, error) with references to the new `strings.xml` entries, improving maintainability and preparing for future internationalization. * feat(logging): Integrate SLF4J for detailed agent logging Replaced standard Android `Log` calls with an SLF4J logger in `ChatViewModel` to provide more structured and detailed logging for the AI agent workflow. Key changes: - Initialized an SLF4J logger instance. - Added logs for AI backend initialization, model loading successes and failures, and the start of the agent workflow. - Included detailed debug logs for agent requests and responses, capturing the prompt, history size, response text, and report. - Logged significant events like workflow completion time, cancellation by the user, and unexpected errors. - Removed an unused private function `constructFullPrompt`. * feat(agent): Add `Cancelling` state for immediate user feedback Introduces a new `Cancelling` state to the `AgentState` sealed class. This allows the UI to provide immediate feedback when a user requests to stop a generation. The key changes are: - A new `AgentState.Cancelling` is added to represent the cancellation request period. - `ChatViewModel` now sets the state to `Cancelling` at the beginning of `stopAgentResponse()` before the agent and job are actually stopped. - `ChatFragment` observes this new state and updates the UI accordingly, showing a "Stopping..." message and disabling the "Stop" button to prevent multiple clicks. - The UI logic for other states (`Idle`, `Processing`, `Error`) has been refined for better consistency and clarity. * fix(agent): Improve UI state management and agent lifecycle This commit introduces several improvements to the agent's UI and lifecycle management: - Prevents sending new messages while the agent is processing, cancelling, or in an error state. - Disables the send button when the input is empty and re-enables it correctly based on agent state. - The send button is now hidden and replaced by a "Stop Generation" button during processing, providing a clearer user interface. - Refactors the `ChatViewModel` to create the AI backend repository only when necessary (e.g., on first use or when settings change), rather than on every message. - Adds the original user prompt to `sendMessage` to ensure it's displayed correctly, even if a more complex prompt is constructed for the agent. * Fix: Add reinterpret_cast for AttachCurrentThread The first argument to `AttachCurrentThread` expects a `void**`, but a `JNIEnv**` was being passed. This change adds the required `reinterpret_cast` to ensure type correctness and resolve potential compilation issues with newer NDK versions. * Fix: Use correct AttachCurrentThread signature for C++ The signature for `AttachCurrentThread` differs between C and C++. This change adds an `#ifdef __cplusplus` check to use the correct method signature, ensuring the JNIEnv is properly passed and avoiding compilation errors in C++ mode. * Fix(llama): Update JNI thread attachment and build scripts Updates the JNI `AttachCurrentThread` call to use the correct signature for the Android NDK C++ environment, with a fallback for the C signature. This resolves potential build and runtime issues related to thread attachment. Additionally, this change: - Replaces the deprecated `llama_new_context_with_model` with `llama_init_from_model`. - Cleans up comments and adds a missing include directory in `CMakeLists.txt`. * Fix: Correctly attach JNI thread in C++ The conditional compilation for `AttachCurrentThread` was unnecessary, as the NDK's C++ signature (`JNIEnv**`) is the correct one to use. This change removes the redundant conditional and the fallback C signature. Additionally, the Android NDK include directory is now prioritized in `CMakeLists.txt` by adding the `BEFORE` keyword to `include_directories`. * Fix: Correctly attach JNI thread in C++ The conditional compilation for `AttachCurrentThread` was unnecessary, as the NDK's C++ signature (`JNIEnv**`) is the correct one to use. This change removes the redundant conditional and the fallback C signature. Additionally, the Android NDK include directory is now prioritized in `CMakeLists.txt` by adding the `BEFORE` keyword to `include_directories`. * feat: Support JNI headers with void** for AttachCurrentThread Adds a templated `attach_current_thread` wrapper to handle different signatures of `AttachCurrentThread` in JNI headers. This ensures compatibility with headers that use `void**` for the `JNIEnv` argument, such as in the Flox environment, in addition to the standard `JNIEnv**`. Additionally, this change silences a CMake warning by explicitly setting the `SYSTEM_INCLUDE_DIRECTORIES` property. * feat(editor): Implement resizable side panel for Agent This commit introduces a new resizable side panel, primarily for the AI Agent feature, replacing the previous right-side drawer. This improves user experience on tablets and in landscape mode by allowing the user to view and interact with the agent alongside their code. Key changes: - Replaced the right `NavigationView` with a `FragmentContainerView` within a `ConstraintLayout` for landscape/tablet layouts. - Implemented a draggable divider to resize the main content and the new side panel. The panel width can be adjusted between 40% and 90% of the screen. - For portrait mode, the panel is implemented as a `BottomSheet` that can be toggled. - A new `AgentPanelController` interface is introduced to abstract the logic for toggling the panel's visibility. - The left swipe gesture in the editor now toggles this new agent panel instead of opening a right-side drawer. - The logic for displaying the right sidebar based on experimental flags in `ProjectHandlerActivity` has been removed in favor of this new implementation. * animate * refactor(editor): Unify landscape and portrait editor layouts This commit refactors the editor to use a single `ConstraintLayout`-based layout for both portrait and landscape orientations, removing the separate `layout-port` version. Key changes: - Deleted `layout-port/activity_editor.xml`, which used a `CoordinatorLayout` and `BottomSheetBehavior`. - The unified `activity_editor.xml` now manages the right-side agent panel with a `Guideline`. - The right panel now starts hidden by default (`guidePercent="1.0"`, `visibility="gone"`). - Removed the Floating Action Button (FAB) previously used to toggle the agent panel. - Updated `BaseEditorActivity.kt` to handle the new unified layout logic: - Removed logic specific to the old portrait `BottomSheet`. - `toggleAgentPanel` now animates the `Guideline` percentage to show or hide the panel. - Implemented snapping behavior for the resizable panel: it can snap to a nearly full-screen state or be dismissed if dragged past certain thresholds. * feat(editor): Set minimum width for editor panel Introduces a minimum width of 200dp for the editor panel when resizing with the vertical guideline. The drag gesture now respects this minimum width, preventing the editor from becoming too narrow. When the drag is released, the guideline will snap to hide the right-hand panel if the editor's width is less than the defined minimum, ensuring a better user experience. * feat(editor): Set minimum width for editor panel Introduces a minimum width of 200dp for the editor panel when resizing with the vertical guideline. The drag gesture now respects this minimum width, preventing the editor from becoming too narrow. When the drag is released, the guideline will snap to hide the right-hand panel if the editor's width is less than the defined minimum, ensuring a better user experience. * Refactor: Change Build Variants sidebar location and remove comment - The location for the `BuildVariantsSidebarAction` has been updated from `EDITOR_RIGHT_SIDEBAR` to `EDITOR_SIDEBAR`. - A redundant comment checking the active layout in `BaseEditorActivity` has been removed. * feat: Relocate sidebar menu items and add plugin build directories to gitignore The menu gravity in the right editor sidebar has been changed from "bottom" to "top", moving the menu items to the top of the sidebar. Additionally, build directories for `plugin-api`, and `plugin-manager` have been added to the `.gitignore` file to prevent them from being committed to the repository. * comments * refactor(agent): Improve GeminiClient error handling and structure This commit refactors the `GeminiClient` to improve its structure, error handling, and robustness. Key changes include: * Trimming whitespace from the API key to prevent authentication issues. * Introducing a centralized `executeWithRetry` function to handle `ServerException` (5xx errors) with a retry mechanism and to sanitize `ClientException` (4xx errors) by throwing a generic error message. This prevents sensitive information like API keys from being exposed in logs or exceptions. * Breaking down the `generateContent` method into smaller, single-responsibility helper functions (`buildGenerateContentConfig`, `executeWithRetry`, `validateResponse`) for better readability and maintenance. * The core logic for API calls, including retry attempts for server errors and tool configuration, is now more organized and resilient. * feat(settings): Improve Gemini API Key management UI This commit refactors the Gemini API Key settings screen to improve user experience and security. Key changes include: - A new UI state that hides the API key input field after a key is saved, showing the save date instead. - Added "Edit" and "Clear" buttons to allow users to modify or remove their saved key. - The timestamp of when the API key was saved is now stored and displayed. - The underlying `EncryptedSharedPreferences` implementation is updated to use the recommended `MasterKeys` builder. * feat(settings): Improve Gemini API Key management UI This commit refactors the Gemini API Key settings screen to improve user experience and security. Key changes include: - A new UI state that hides the API key input field after a key is saved, showing the save date instead. - Added "Edit" and "Clear" buttons to allow users to modify or remove their saved key. - The timestamp of when the API key was saved is now stored and displayed. - The underlying `EncryptedSharedPreferences` implementation is updated to use the recommended `MasterKeys` builder. * feat: Use string resources for chat UI Extract hardcoded strings in the chat fragment and menu to `strings.xml` for localization. Changes include: - Use string resources for the chat toolbar title and menu items. - Update the `ic_add` drawable to use the theme attribute `?attr/colorOnSurfaceVariant` for its fill color, improving theme adaptability. * refactor: Move AI Agent strings and clean up menus Move AI-related string resources from the `app` module to the `layouteditor` module to centralize them. This also includes adding new strings for the AI settings UI, such as API key status messages. Other changes include: - Refactor the AI settings UI to use the new string resources. - Clean up the chat menu by removing export, share, and delete options. - Adjust the size of `chat_history.xml` and `ic_back.xml` drawables. * env removed * Fix: Minor cleanups and typo fixes This commit includes several minor cleanups and corrections: - Fixes typos in comments within ggml and agent state. - Removes a period and corrects an ellipsis in string resources. - Corrects a variable assignment in wasm quantization. - Fixes a macro name from `GGML_COMPUTE_FP32_TO_FP16` to `GGML_CPU_COMPUTE_FP32_TO_FP16`. - Removes a trailing semicolon in `LLamaAndroid.kt`. - Deletes commented-out code explaining how to write GGUF files. * feat(build): Replace llama module with prebuilt AARs Removes the `:llama` project module from the build. Instead, introduces `v7` and `v8` implementation configurations that point to prebuilt AAR files (`llama-v7-release.aar` and `llama-v8-release.aar`) located in the `libs/` directory. This change modularizes the Llama dependency, allowing for different versions to be included as precompiled artifacts rather than source code. * feat(build): Replace llama module with prebuilt AARs Removes the `:llama` project module from the build. Instead, introduces `v7` and `v8` implementation configurations that point to prebuilt AAR files (`llama-v7-release.aar` and `llama-v8-release.aar`) located in the `libs/` directory. This change modularizes the Llama dependency, allowing for different versions to be included as precompiled artifacts rather than source code. * feat: Bundle architecture-specific Llama AAR into offline zip This change updates the `createOfflineZip` Gradle task to include the appropriate Llama AAR file based on the target architecture (`arm64-v8a` or `armeabi-v7a`). The script now: - Determines the correct AAR file path from `app/libs/`. - Adds the selected AAR to the offline zip under the generic path `dynamic_libs/llama.aar`. - Throws an error if the AAR file for the specified architecture is not found. * feat: Bundle architecture-specific Llama AAR into offline zip This change updates the `createOfflineZip` Gradle task to include the appropriate Llama AAR file based on the target architecture (`arm64-v8a` or `armeabi-v7a`). The script now: - Determines the correct AAR file path from `app/libs/`. - Adds the selected AAR to the offline zip under the generic path `dynamic_libs/llama.aar`. - Throws an error if the AAR file for the specified architecture is not found. * feat: Add support for installing Llama AAR Adds the logic to install the Llama AAR file from bundled, Brotli-compressed assets. The correct AAR (`llama-v7.aar` for ARM or `llama-v8.aar` for AARCH64) is chosen based on the device's CPU architecture and extracted to the `dynamic_libs` directory. * feat: Extract llama.aar to dynamic_libs Extracts the `llama.aar` file from the split assets into the `dynamic_libs` directory within the app's private storage. * feat: Extract llama.aar to dynamic_libs Extracts the `llama.aar` file from the split assets into the `dynamic_libs` directory within the app's private storage. * feat: Add dynamic library loader for Llama AAR Adds a `DynamicLibraryLoader` utility to dynamically load the `llama.aar` at runtime. This new loader performs the following steps: - Locates the `llama.aar` file extracted from assets. - Unzips the AAR into a versioned directory within the app's `codeCacheDir` to manage updates. - Creates and returns a `DexClassLoader` configured with the path to `classes.jar` and the appropriate native libraries (`.so` files) for the device's ABI. * feat: Lazily initialize LLM inference engine Dynamically load the Llama AAR and initialize the `LlamaAndroid` controller via reflection. This avoids a hard dependency and allows the engine to be initialized on-demand. The `LlmInferenceEngine` is updated to: - Add a new `initialize()` method that must be called before using the engine. - Replace direct calls to the static `LLamaAndroid.instance()` with a nullable `llamaController`. - Add checks to ensure the engine is initialized before performing operations like loading a model or running inference. * feat: Remove local llama AAR dependencies Removes the local AAR file dependencies for `llama-v7-release.aar` and `llama-v8-release.aar`. The implementation configurations `v7Implementation` and `v8Implementation` have been deleted from the `app/build.gradle.kts` file. * feat: Add llama-api module for abstraction Introduces a new `llama-api` module to abstract the Llama inference engine implementation. This allows for cleaner separation of concerns and easier future updates. Changes include: - Creating the `llama-api` Gradle module. - Defining an `ILlamaController` interface. - Updating `LlmInferenceEngine` to use the new interface instead of the concrete implementation. - Including the new `llama-api` and `llama-impl` modules in the project build. - Adding necessary dependencies to the new module. * feat: Bump Java version and update Llama AAR path - Bumps the Java source and target compatibility from 11 to 17 in the `llama-api` module. - Updates the AAR filename for the Llama library from `llama-v*-release.aar` to `llama-impl-v*-release.aar`. * feat: Pre-DEX Llama AAR before packaging into assets This commit modifies the build process to convert the `classes.jar` inside the Llama AAR to `classes.dex` *before* packaging it into the final `assets-*.zip` file. This is accomplished by using the D8 tool. Changes include: - Extracting `classes.jar` from the pre-built `llama-impl-*.aar`. - Running the `d8` command-line tool to convert the JAR to a DEX file. - Repackaging the AAR, replacing the original `classes.jar` with the newly generated `classes.dex`. - Adding `dependsOn` to the `assembleV*Assets` tasks to ensure the Llama AARs are built first. - In `LlmInferenceEngine`, `initialize()` is now called automatically if needed when loading a model, preventing a potential `Engine not initialized` error. * feat(llama): Integrate llama.cpp as a native library This commit integrates `llama.cpp` directly as a native library within the new `llama-impl` module, replacing the previous dynamic AAR loading mechanism. Key changes: - Adds a new `llama-impl` Android library module containing the C++ source, JNI bindings, and a `LLamaAndroid.kt` controller. - Uses CMake to build `llama.cpp` and the JNI wrapper as part of the standard Gradle build process. - The `createAssetsZip` task in `app/build.gradle.kts` is heavily refactored: - It now uses the D8 tool to compile the `llama-impl` module's `classes.jar` and all its dependencies into a `classes.dex` file. - The classpath for D8 is correctly constructed by resolving the `llama-impl` module's runtime configuration, ensuring all necessary classes are included. - Core library desugaring is enabled for Java 17 compatibility. - ProGuard is enabled for release builds in the `app` module. - The `DynamicLibraryLoader` is updated to load `classes.dex` instead of `classes.jar`, simplifying the class loading process. * feat(llama): Integrate llama.cpp as a native library This commit integrates `llama.cpp` directly as a native library within the new `llama-impl` module, replacing the previous dynamic AAR loading mechanism. Key changes: - Adds a new `llama-impl` Android library module containing the C++ source, JNI bindings, and a `LLamaAndroid.kt` controller. - Uses CMake to build `llama.cpp` and the JNI wrapper as part of the standard Gradle build process. - The `createAssetsZip` task in `app/build.gradle.kts` is heavily refactored: - It now uses the D8 tool to compile the `llama-impl` module's `classes.jar` and all its dependencies into a `classes.dex` file. - The classpath for D8 is correctly constructed by resolving the `llama-impl` module's runtime configuration, ensuring all necessary classes are included. - Core library desugaring is enabled for Java 17 compatibility. - ProGuard is enabled for release builds in the `app` module. - The `DynamicLibraryLoader` is updated to load `classes.dex` instead of `classes.jar`, simplifying the class loading process. * feat: Update dependencies and improve Llama C++ implementation This commit introduces several updates and fixes: - Updates the Koin dependency to version 4.1.1 and moves it to the `libs.versions.toml` catalog for centralized version management. - Removes unused `okhttp` and `kotlinx-serialization-json` dependencies from `app/build.gradle.kts`. - Implements a more robust memory deallocation for `llama_batch` in the C++ layer to prevent memory leaks. - Adds an error-handling mechanism in `llama-android.cpp` to throw a `java/lang/IllegalArgumentException` when the prompt size exceeds the model's context size, preventing native crashes. * feat: Update dependencies and improve Llama C++ implementation This commit introduces several updates and fixes: - Updates the Koin dependency to version 4.1.1 and moves it to the `libs.versions.toml` catalog for centralized version management. - Removes unused `okhttp` and `kotlinx-serialization-json` dependencies from `app/build.gradle.kts`. - Implements a more robust memory deallocation for `llama_batch` in the C++ layer to prevent memory leaks. - Adds an error-handling mechanism in `llama-android.cpp` to throw a `java/lang/IllegalArgumentException` when the prompt size exceeds the model's context size, preventing native crashes. * build: Disable minification for release builds This commit removes the ProGuard configuration for the `release` build type, effectively disabling code shrinking, obfuscation, and optimization. The `isMinifyEnabled = true` flag and the `proguardFiles` configuration have been removed from the `release` block in `app/build.gradle.kts`. * build: Disable minification for release builds This commit removes the ProGuard configuration for the `release` build type, effectively disabling code shrinking, obfuscation, and optimization. The `isMinifyEnabled = true` flag and the `proguardFiles` configuration have been removed from the `release` block in `app/build.gradle.kts`. * refactor(agent): Decouple LlmInferenceEngine from native library and add initialization state This commit refactors the `LlmInferenceEngine` to decouple it from the static `LLamaAndroid` library. The engine now dynamically loads the native library and its classes at runtime. **Key Changes:** - **Dynamic Library Loading:** `LlmInferenceEngine` no longer directly accesses `LLamaAndroid.instance()`. Instead, it uses a `DynamicLibraryLoader` to get a `ClassLoader` and loads the "android.llama.cpp.LLamaAndroid" class via reflection. - **Engine Initialization State:** A new `initialize` method and `EngineState` (`Uninitialized`, `Initializing`, `Initialized`, `Error`) have been introduced. The engine must be successfully initialized before any models can be loaded or inference can be run. - **ViewModel and UI Updates:** - `AiSettingsViewModel` now manages the `EngineState` and exposes it via LiveData. - The `AiSettingsFragment` UI observes this state, disabling model selection buttons until the engine is initialized and displaying the current status (e.g., "Initializing engine...", "Engine is ready."). - Model loading is now prevented if the engine is not ready. - **Interface-based Control:** The engine now interacts with the loaded llama instance through the `ILlamaController` interface, improving abstraction. - **Code Cleanup:** Unused methods and redundant logic in `AiSettingsViewModel` have been removed, and the view model's responsibility is now clearly focused on state management and orchestrating calls to the engine. * refactor: Move shared string resources to the resources module The following string resources have been moved from the `app` module to the shared `resources` module to promote reusability and centralize common text: - `retry` - `open_ai_settings` - `new_chat` - `ai_setting_initializing_engine` - `ai_setting_engine_ready` * refactor: Replace getBaseInstance() with baseInstance property access * fix: support uncompressed llama AAR assets as fallback - In `BundledAssetsInstaller`, try loading the uncompressed llama AAR asset if the Brotli-compressed version is not found. - Add Gradle tasks `bundleV7LlamaAssets` and `bundleV8LlamaAssets` to extract `llama.aar` from the assets zip and copy it to the release assets directory. - In the new Gradle tasks, attempt to compress the AAR with `brotli` if available; otherwise, copy it uncompressed. - Hook these new bundling tasks into the build process (via `assembleV7Release` and `assembleV8Release`). --- app/build.gradle.kts | 296 +- .../agent/fragments/AiSettingsFragment.kt | 43 +- .../agent/repository/LlmInferenceEngine.kt | 134 +- .../repository/LlmInferenceEngineProvider.kt | 2 - .../agent/viewmodel/AiSettingsViewModel.kt | 228 +- .../assets/AssetsInstallationHelper.kt | 4 +- .../assets/BundledAssetsInstaller.kt | 46 +- .../androidide/assets/SplitAssetsInstaller.kt | 11 +- .../androidide/utils/DynamicLibraryLoader.kt | 75 + .../res/layout/layout_settings_local_llm.xml | 8 +- gradle/libs.versions.toml | 13 +- llama-api/.gitignore | 1 + llama-api/build.gradle.kts | 16 + .../llamacpp/api/ILlamaController.kt | 21 + llama-impl/.gitignore | 1 + llama-impl/build.gradle.kts | 68 + llama-impl/proguard-rules.pro | 8 + llama-impl/src/main/cpp/CMakeLists.txt | 53 + llama-impl/src/main/cpp/llama-android.cpp | 608 + .../java/android/llama/cpp/LLamaAndroid.kt | 267 + resources/src/main/res/values/strings.xml | 2 + settings.gradle.kts | 2 + subprojects/llama.cpp/CMakeLists.txt | 273 + .../llama.cpp/cmake/arm64-apple-clang.cmake | 16 + .../llama.cpp/cmake/arm64-windows-llvm.cmake | 16 + subprojects/llama.cpp/cmake/build-info.cmake | 64 + subprojects/llama.cpp/cmake/common.cmake | 35 + subprojects/llama.cpp/cmake/git-vars.cmake | 22 + .../llama.cpp/cmake/llama-config.cmake.in | 30 + subprojects/llama.cpp/cmake/llama.pc.in | 10 + .../llama.cpp/cmake/x64-windows-llvm.cmake | 5 + subprojects/llama.cpp/common/CMakeLists.txt | 162 + subprojects/llama.cpp/common/arg.cpp | 4079 +++ subprojects/llama.cpp/common/arg.h | 109 + subprojects/llama.cpp/common/base64.hpp | 390 + .../llama.cpp/common/build-info.cpp.in | 4 + subprojects/llama.cpp/common/chat-parser.cpp | 411 + subprojects/llama.cpp/common/chat-parser.h | 133 + subprojects/llama.cpp/common/chat.cpp | 2935 ++ subprojects/llama.cpp/common/chat.h | 238 + subprojects/llama.cpp/common/common.cpp | 1684 + subprojects/llama.cpp/common/common.h | 782 + subprojects/llama.cpp/common/console.cpp | 508 + subprojects/llama.cpp/common/console.h | 22 + subprojects/llama.cpp/common/json-partial.cpp | 291 + subprojects/llama.cpp/common/json-partial.h | 38 + .../common/json-schema-to-grammar.cpp | 1078 + .../llama.cpp/common/json-schema-to-grammar.h | 22 + subprojects/llama.cpp/common/llguidance.cpp | 254 + subprojects/llama.cpp/common/log.cpp | 457 + subprojects/llama.cpp/common/log.h | 112 + subprojects/llama.cpp/common/ngram-cache.cpp | 300 + subprojects/llama.cpp/common/ngram-cache.h | 105 + .../llama.cpp/common/regex-partial.cpp | 205 + subprojects/llama.cpp/common/regex-partial.h | 61 + subprojects/llama.cpp/common/sampling.cpp | 663 + subprojects/llama.cpp/common/sampling.h | 121 + subprojects/llama.cpp/common/speculative.cpp | 369 + subprojects/llama.cpp/common/speculative.h | 35 + subprojects/llama.cpp/ggml/.gitignore | 2 + subprojects/llama.cpp/ggml/CMakeLists.txt | 448 + .../llama.cpp/ggml/cmake/GitVars.cmake | 22 + subprojects/llama.cpp/ggml/cmake/common.cmake | 50 + .../llama.cpp/ggml/cmake/ggml-config.cmake.in | 191 + .../llama.cpp/ggml/include/ggml-alloc.h | 76 + .../llama.cpp/ggml/include/ggml-backend.h | 369 + subprojects/llama.cpp/ggml/include/ggml-cpp.h | 39 + subprojects/llama.cpp/ggml/include/ggml-cpu.h | 145 + subprojects/llama.cpp/ggml/include/ggml-opt.h | 256 + subprojects/llama.cpp/ggml/include/ggml.h | 2546 ++ subprojects/llama.cpp/ggml/include/gguf.h | 202 + subprojects/llama.cpp/ggml/src/CMakeLists.txt | 419 + subprojects/llama.cpp/ggml/src/ggml-alloc.c | 1028 + .../llama.cpp/ggml/src/ggml-backend-impl.h | 258 + .../llama.cpp/ggml/src/ggml-backend-reg.cpp | 599 + .../llama.cpp/ggml/src/ggml-backend.cpp | 2209 ++ subprojects/llama.cpp/ggml/src/ggml-common.h | 1878 ++ .../ggml/src/ggml-cpu/CMakeLists.txt | 609 + .../llama.cpp/ggml/src/ggml-cpu/amx/amx.cpp | 223 + .../llama.cpp/ggml/src/ggml-cpu/amx/amx.h | 8 + .../llama.cpp/ggml/src/ggml-cpu/amx/common.h | 91 + .../llama.cpp/ggml/src/ggml-cpu/amx/mmq.cpp | 2512 ++ .../llama.cpp/ggml/src/ggml-cpu/amx/mmq.h | 10 + .../ggml/src/ggml-cpu/arch-fallback.h | 215 + .../ggml/src/ggml-cpu/arch/arm/cpu-feats.cpp | 94 + .../ggml/src/ggml-cpu/arch/arm/quants.c | 3650 +++ .../ggml/src/ggml-cpu/arch/arm/repack.cpp | 1891 ++ .../ggml/src/ggml-cpu/arch/loongarch/quants.c | 2160 ++ .../src/ggml-cpu/arch/powerpc/cpu-feats.cpp | 82 + .../ggml/src/ggml-cpu/arch/powerpc/quants.c | 2305 ++ .../ggml/src/ggml-cpu/arch/riscv/quants.c | 1897 ++ .../ggml/src/ggml-cpu/arch/riscv/repack.cpp | 342 + .../ggml/src/ggml-cpu/arch/s390/quants.c | 1371 + .../ggml/src/ggml-cpu/arch/wasm/quants.c | 1221 + .../ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp | 327 + .../ggml/src/ggml-cpu/arch/x86/quants.c | 3820 +++ .../ggml/src/ggml-cpu/arch/x86/repack.cpp | 6307 ++++ .../ggml/src/ggml-cpu/binary-ops.cpp | 158 + .../llama.cpp/ggml/src/ggml-cpu/binary-ops.h | 16 + .../ggml/src/ggml-cpu/cmake/FindSIMD.cmake | 100 + .../llama.cpp/ggml/src/ggml-cpu/common.h | 87 + .../ggml/src/ggml-cpu/ggml-cpu-impl.h | 524 + .../llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c | 3565 +++ .../llama.cpp/ggml/src/ggml-cpu/ggml-cpu.cpp | 672 + .../llama.cpp/ggml/src/ggml-cpu/hbm.cpp | 55 + subprojects/llama.cpp/ggml/src/ggml-cpu/hbm.h | 8 + .../ggml/src/ggml-cpu/kleidiai/kernels.cpp | 471 + .../ggml/src/ggml-cpu/kleidiai/kernels.h | 101 + .../ggml/src/ggml-cpu/kleidiai/kleidiai.cpp | 566 + .../ggml/src/ggml-cpu/kleidiai/kleidiai.h | 17 + .../ggml/src/ggml-cpu/llamafile/sgemm.cpp | 2952 ++ .../ggml/src/ggml-cpu/llamafile/sgemm.h | 19 + .../llama.cpp/ggml/src/ggml-cpu/ops.cpp | 10188 ++++++ subprojects/llama.cpp/ggml/src/ggml-cpu/ops.h | 115 + .../llama.cpp/ggml/src/ggml-cpu/quants.c | 1193 + .../llama.cpp/ggml/src/ggml-cpu/quants.h | 97 + .../llama.cpp/ggml/src/ggml-cpu/repack.cpp | 1982 ++ .../llama.cpp/ggml/src/ggml-cpu/repack.h | 120 + .../ggml/src/ggml-cpu/simd-mappings.h | 1213 + .../llama.cpp/ggml/src/ggml-cpu/traits.cpp | 36 + .../llama.cpp/ggml/src/ggml-cpu/traits.h | 38 + .../llama.cpp/ggml/src/ggml-cpu/unary-ops.cpp | 186 + .../llama.cpp/ggml/src/ggml-cpu/unary-ops.h | 28 + .../llama.cpp/ggml/src/ggml-cpu/vec.cpp | 489 + subprojects/llama.cpp/ggml/src/ggml-cpu/vec.h | 1470 + subprojects/llama.cpp/ggml/src/ggml-impl.h | 622 + subprojects/llama.cpp/ggml/src/ggml-opt.cpp | 1093 + subprojects/llama.cpp/ggml/src/ggml-quants.c | 5324 ++++ subprojects/llama.cpp/ggml/src/ggml-quants.h | 106 + .../llama.cpp/ggml/src/ggml-threading.cpp | 12 + .../llama.cpp/ggml/src/ggml-threading.h | 14 + subprojects/llama.cpp/ggml/src/ggml.c | 7209 +++++ subprojects/llama.cpp/ggml/src/ggml.cpp | 26 + subprojects/llama.cpp/ggml/src/gguf.cpp | 1433 + subprojects/llama.cpp/include/llama-cpp.h | 30 + subprojects/llama.cpp/include/llama.h | 1458 + subprojects/llama.cpp/src/CMakeLists.txt | 50 + subprojects/llama.cpp/src/llama-adapter.cpp | 511 + subprojects/llama.cpp/src/llama-adapter.h | 84 + subprojects/llama.cpp/src/llama-arch.cpp | 2456 ++ subprojects/llama.cpp/src/llama-arch.h | 517 + subprojects/llama.cpp/src/llama-batch.cpp | 894 + subprojects/llama.cpp/src/llama-batch.h | 163 + subprojects/llama.cpp/src/llama-chat.cpp | 813 + subprojects/llama.cpp/src/llama-chat.h | 66 + subprojects/llama.cpp/src/llama-context.cpp | 2934 ++ subprojects/llama.cpp/src/llama-context.h | 328 + subprojects/llama.cpp/src/llama-cparams.cpp | 5 + subprojects/llama.cpp/src/llama-cparams.h | 41 + subprojects/llama.cpp/src/llama-grammar.cpp | 1271 + subprojects/llama.cpp/src/llama-grammar.h | 176 + subprojects/llama.cpp/src/llama-graph.cpp | 2004 ++ subprojects/llama.cpp/src/llama-graph.h | 861 + subprojects/llama.cpp/src/llama-hparams.cpp | 218 + subprojects/llama.cpp/src/llama-hparams.h | 253 + subprojects/llama.cpp/src/llama-impl.cpp | 180 + subprojects/llama.cpp/src/llama-impl.h | 67 + subprojects/llama.cpp/src/llama-io.cpp | 15 + subprojects/llama.cpp/src/llama-io.h | 39 + .../llama.cpp/src/llama-kv-cache-iswa.cpp | 328 + .../llama.cpp/src/llama-kv-cache-iswa.h | 148 + subprojects/llama.cpp/src/llama-kv-cache.cpp | 2079 ++ subprojects/llama.cpp/src/llama-kv-cache.h | 407 + subprojects/llama.cpp/src/llama-kv-cells.h | 491 + .../llama.cpp/src/llama-memory-hybrid.cpp | 267 + .../llama.cpp/src/llama-memory-hybrid.h | 150 + .../llama.cpp/src/llama-memory-recurrent.cpp | 1182 + .../llama.cpp/src/llama-memory-recurrent.h | 200 + subprojects/llama.cpp/src/llama-memory.cpp | 51 + subprojects/llama.cpp/src/llama-memory.h | 129 + subprojects/llama.cpp/src/llama-mmap.cpp | 622 + subprojects/llama.cpp/src/llama-mmap.h | 77 + .../llama.cpp/src/llama-model-loader.cpp | 1395 + .../llama.cpp/src/llama-model-loader.h | 182 + .../llama.cpp/src/llama-model-saver.cpp | 301 + subprojects/llama.cpp/src/llama-model-saver.h | 42 + subprojects/llama.cpp/src/llama-model.cpp | 19701 ++++++++++++ subprojects/llama.cpp/src/llama-model.h | 505 + subprojects/llama.cpp/src/llama-quant.cpp | 1217 + subprojects/llama.cpp/src/llama-quant.h | 1 + subprojects/llama.cpp/src/llama-sampling.cpp | 2762 ++ subprojects/llama.cpp/src/llama-sampling.h | 32 + subprojects/llama.cpp/src/llama-vocab.cpp | 3983 +++ subprojects/llama.cpp/src/llama-vocab.h | 214 + subprojects/llama.cpp/src/llama.cpp | 427 + subprojects/llama.cpp/src/unicode-data.cpp | 7034 +++++ subprojects/llama.cpp/src/unicode-data.h | 20 + subprojects/llama.cpp/src/unicode.cpp | 1094 + subprojects/llama.cpp/src/unicode.h | 72 + .../llama.cpp/vendor/minja/chat-template.hpp | 550 + subprojects/llama.cpp/vendor/minja/minja.hpp | 3009 ++ .../llama.cpp/vendor/nlohmann/json.hpp | 25526 ++++++++++++++++ .../llama.cpp/vendor/nlohmann/json_fwd.hpp | 187 + 193 files changed, 189724 insertions(+), 218 deletions(-) create mode 100644 app/src/main/java/com/itsaky/androidide/utils/DynamicLibraryLoader.kt create mode 100644 llama-api/.gitignore create mode 100644 llama-api/build.gradle.kts create mode 100644 llama-api/src/main/java/com/itsaky/androidide/llamacpp/api/ILlamaController.kt create mode 100644 llama-impl/.gitignore create mode 100644 llama-impl/build.gradle.kts create mode 100644 llama-impl/proguard-rules.pro create mode 100644 llama-impl/src/main/cpp/CMakeLists.txt create mode 100644 llama-impl/src/main/cpp/llama-android.cpp create mode 100644 llama-impl/src/main/java/android/llama/cpp/LLamaAndroid.kt create mode 100644 subprojects/llama.cpp/CMakeLists.txt create mode 100644 subprojects/llama.cpp/cmake/arm64-apple-clang.cmake create mode 100644 subprojects/llama.cpp/cmake/arm64-windows-llvm.cmake create mode 100644 subprojects/llama.cpp/cmake/build-info.cmake create mode 100644 subprojects/llama.cpp/cmake/common.cmake create mode 100644 subprojects/llama.cpp/cmake/git-vars.cmake create mode 100644 subprojects/llama.cpp/cmake/llama-config.cmake.in create mode 100644 subprojects/llama.cpp/cmake/llama.pc.in create mode 100644 subprojects/llama.cpp/cmake/x64-windows-llvm.cmake create mode 100644 subprojects/llama.cpp/common/CMakeLists.txt create mode 100644 subprojects/llama.cpp/common/arg.cpp create mode 100644 subprojects/llama.cpp/common/arg.h create mode 100644 subprojects/llama.cpp/common/base64.hpp create mode 100644 subprojects/llama.cpp/common/build-info.cpp.in create mode 100644 subprojects/llama.cpp/common/chat-parser.cpp create mode 100644 subprojects/llama.cpp/common/chat-parser.h create mode 100644 subprojects/llama.cpp/common/chat.cpp create mode 100644 subprojects/llama.cpp/common/chat.h create mode 100644 subprojects/llama.cpp/common/common.cpp create mode 100644 subprojects/llama.cpp/common/common.h create mode 100644 subprojects/llama.cpp/common/console.cpp create mode 100644 subprojects/llama.cpp/common/console.h create mode 100644 subprojects/llama.cpp/common/json-partial.cpp create mode 100644 subprojects/llama.cpp/common/json-partial.h create mode 100644 subprojects/llama.cpp/common/json-schema-to-grammar.cpp create mode 100644 subprojects/llama.cpp/common/json-schema-to-grammar.h create mode 100644 subprojects/llama.cpp/common/llguidance.cpp create mode 100644 subprojects/llama.cpp/common/log.cpp create mode 100644 subprojects/llama.cpp/common/log.h create mode 100644 subprojects/llama.cpp/common/ngram-cache.cpp create mode 100644 subprojects/llama.cpp/common/ngram-cache.h create mode 100644 subprojects/llama.cpp/common/regex-partial.cpp create mode 100644 subprojects/llama.cpp/common/regex-partial.h create mode 100644 subprojects/llama.cpp/common/sampling.cpp create mode 100644 subprojects/llama.cpp/common/sampling.h create mode 100644 subprojects/llama.cpp/common/speculative.cpp create mode 100644 subprojects/llama.cpp/common/speculative.h create mode 100644 subprojects/llama.cpp/ggml/.gitignore create mode 100644 subprojects/llama.cpp/ggml/CMakeLists.txt create mode 100644 subprojects/llama.cpp/ggml/cmake/GitVars.cmake create mode 100644 subprojects/llama.cpp/ggml/cmake/common.cmake create mode 100644 subprojects/llama.cpp/ggml/cmake/ggml-config.cmake.in create mode 100644 subprojects/llama.cpp/ggml/include/ggml-alloc.h create mode 100644 subprojects/llama.cpp/ggml/include/ggml-backend.h create mode 100644 subprojects/llama.cpp/ggml/include/ggml-cpp.h create mode 100644 subprojects/llama.cpp/ggml/include/ggml-cpu.h create mode 100644 subprojects/llama.cpp/ggml/include/ggml-opt.h create mode 100644 subprojects/llama.cpp/ggml/include/ggml.h create mode 100644 subprojects/llama.cpp/ggml/include/gguf.h create mode 100644 subprojects/llama.cpp/ggml/src/CMakeLists.txt create mode 100644 subprojects/llama.cpp/ggml/src/ggml-alloc.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-backend-impl.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-backend-reg.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-backend.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-common.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/CMakeLists.txt create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/amx/amx.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/amx/amx.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/amx/common.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/amx/mmq.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/amx/mmq.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch-fallback.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/arm/cpu-feats.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/arm/quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/arm/repack.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/loongarch/quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/powerpc/cpu-feats.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/powerpc/quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/riscv/quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/riscv/repack.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/s390/quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/wasm/quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/x86/quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/arch/x86/repack.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/binary-ops.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/binary-ops.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/cmake/FindSIMD.cmake create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/common.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/ggml-cpu-impl.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/hbm.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/hbm.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/kleidiai/kernels.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/kleidiai/kernels.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/kleidiai/kleidiai.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/kleidiai/kleidiai.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/llamafile/sgemm.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/llamafile/sgemm.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/ops.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/ops.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/quants.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/repack.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/repack.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/simd-mappings.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/traits.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/traits.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/unary-ops.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/unary-ops.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/vec.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-cpu/vec.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-impl.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-opt.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-quants.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml-quants.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml-threading.cpp create mode 100644 subprojects/llama.cpp/ggml/src/ggml-threading.h create mode 100644 subprojects/llama.cpp/ggml/src/ggml.c create mode 100644 subprojects/llama.cpp/ggml/src/ggml.cpp create mode 100644 subprojects/llama.cpp/ggml/src/gguf.cpp create mode 100644 subprojects/llama.cpp/include/llama-cpp.h create mode 100644 subprojects/llama.cpp/include/llama.h create mode 100644 subprojects/llama.cpp/src/CMakeLists.txt create mode 100644 subprojects/llama.cpp/src/llama-adapter.cpp create mode 100644 subprojects/llama.cpp/src/llama-adapter.h create mode 100644 subprojects/llama.cpp/src/llama-arch.cpp create mode 100644 subprojects/llama.cpp/src/llama-arch.h create mode 100644 subprojects/llama.cpp/src/llama-batch.cpp create mode 100644 subprojects/llama.cpp/src/llama-batch.h create mode 100644 subprojects/llama.cpp/src/llama-chat.cpp create mode 100644 subprojects/llama.cpp/src/llama-chat.h create mode 100644 subprojects/llama.cpp/src/llama-context.cpp create mode 100644 subprojects/llama.cpp/src/llama-context.h create mode 100644 subprojects/llama.cpp/src/llama-cparams.cpp create mode 100644 subprojects/llama.cpp/src/llama-cparams.h create mode 100644 subprojects/llama.cpp/src/llama-grammar.cpp create mode 100644 subprojects/llama.cpp/src/llama-grammar.h create mode 100644 subprojects/llama.cpp/src/llama-graph.cpp create mode 100644 subprojects/llama.cpp/src/llama-graph.h create mode 100644 subprojects/llama.cpp/src/llama-hparams.cpp create mode 100644 subprojects/llama.cpp/src/llama-hparams.h create mode 100644 subprojects/llama.cpp/src/llama-impl.cpp create mode 100644 subprojects/llama.cpp/src/llama-impl.h create mode 100644 subprojects/llama.cpp/src/llama-io.cpp create mode 100644 subprojects/llama.cpp/src/llama-io.h create mode 100644 subprojects/llama.cpp/src/llama-kv-cache-iswa.cpp create mode 100644 subprojects/llama.cpp/src/llama-kv-cache-iswa.h create mode 100644 subprojects/llama.cpp/src/llama-kv-cache.cpp create mode 100644 subprojects/llama.cpp/src/llama-kv-cache.h create mode 100644 subprojects/llama.cpp/src/llama-kv-cells.h create mode 100644 subprojects/llama.cpp/src/llama-memory-hybrid.cpp create mode 100644 subprojects/llama.cpp/src/llama-memory-hybrid.h create mode 100644 subprojects/llama.cpp/src/llama-memory-recurrent.cpp create mode 100644 subprojects/llama.cpp/src/llama-memory-recurrent.h create mode 100644 subprojects/llama.cpp/src/llama-memory.cpp create mode 100644 subprojects/llama.cpp/src/llama-memory.h create mode 100644 subprojects/llama.cpp/src/llama-mmap.cpp create mode 100644 subprojects/llama.cpp/src/llama-mmap.h create mode 100644 subprojects/llama.cpp/src/llama-model-loader.cpp create mode 100644 subprojects/llama.cpp/src/llama-model-loader.h create mode 100644 subprojects/llama.cpp/src/llama-model-saver.cpp create mode 100644 subprojects/llama.cpp/src/llama-model-saver.h create mode 100644 subprojects/llama.cpp/src/llama-model.cpp create mode 100644 subprojects/llama.cpp/src/llama-model.h create mode 100644 subprojects/llama.cpp/src/llama-quant.cpp create mode 100644 subprojects/llama.cpp/src/llama-quant.h create mode 100644 subprojects/llama.cpp/src/llama-sampling.cpp create mode 100644 subprojects/llama.cpp/src/llama-sampling.h create mode 100644 subprojects/llama.cpp/src/llama-vocab.cpp create mode 100644 subprojects/llama.cpp/src/llama-vocab.h create mode 100644 subprojects/llama.cpp/src/llama.cpp create mode 100644 subprojects/llama.cpp/src/unicode-data.cpp create mode 100644 subprojects/llama.cpp/src/unicode-data.h create mode 100644 subprojects/llama.cpp/src/unicode.cpp create mode 100644 subprojects/llama.cpp/src/unicode.h create mode 100644 subprojects/llama.cpp/vendor/minja/chat-template.hpp create mode 100644 subprojects/llama.cpp/vendor/minja/minja.hpp create mode 100644 subprojects/llama.cpp/vendor/nlohmann/json.hpp create mode 100644 subprojects/llama.cpp/vendor/nlohmann/json_fwd.hpp diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6956c9b8b1..32f401d28b 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,14 +16,41 @@ import java.net.URL import java.nio.file.Files import java.nio.file.StandardCopyOption import java.nio.file.attribute.FileTime +import java.util.Locale import java.util.Properties +import java.util.zip.CRC32 import java.util.zip.Deflater import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream -import java.util.zip.CRC32 import kotlin.reflect.jvm.javaMethod +fun TaskContainer.registerD8Task( + taskName: String, + inputJar: File, + outputDex: File +): org.gradle.api.tasks.TaskProvider { + val androidSdkDir = android.sdkDirectory.absolutePath + val buildToolsVersion = android.buildToolsVersion // Gets the version from your project + val d8Executable = File("$androidSdkDir/build-tools/$buildToolsVersion/d8") + + if (!d8Executable.exists()) { + throw FileNotFoundException("D8 executable not found at: ${d8Executable.absolutePath}") + } + + return register(taskName) { + inputs.file(inputJar) + outputs.file(outputDex) + + commandLine( + d8Executable.absolutePath, + "--release", // Enables optimizations + "--output", outputDex.parent, // D8 outputs to a directory + inputJar.absolutePath + ) + } +} + plugins { id("com.android.application") id("kotlin-android") @@ -120,6 +147,11 @@ android { excludes.add("META-INF/gradle/incremental.annotation.processors") } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + isCoreLibraryDesugaringEnabled = true + } } kapt { arguments { arg("eventBusIndex", "${BuildConfig.PACKAGE_NAME}.events.AppEventsIndex") } } @@ -270,10 +302,9 @@ dependencies { implementation(libs.common.markwon.linkify) implementation(libs.commons.text.v1140) - implementation("com.squareup.okhttp3:okhttp:4.12.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation(libs.kotlinx.serialization.json) // Koin for Dependency Injection - implementation("io.insert-koin:koin-android:3.5.3") + implementation(libs.koin.android) implementation(libs.androidx.security.crypto) // Sentry Android SDK (core + replay for quality configuration) @@ -289,8 +320,8 @@ dependencies { implementation(libs.androidx.lifecycle.process) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.google.genai) - "v7Implementation"(files("libs/v7/llama-v7-release.aar")) - "v8Implementation"(files("libs/v8/llama-v8-release.aar")) + implementation(project(":llama-api")) + coreLibraryDesugaring(libs.desugar.jdk.libs.v215) } tasks.register("downloadDocDb") { @@ -340,30 +371,118 @@ tasks.register("downloadDocDb") { } fun createAssetsZip(arch: String) { - val outputDir = - project.layout.buildDirectory - .dir("outputs/assets") - .get() - .asFile - if (!outputDir.exists()) { - outputDir.mkdirs() - println("Creating output directory: ${outputDir.absolutePath}") - } + val outputDir = + project.layout.buildDirectory + .dir("outputs/assets") + .get() + .asFile + if (!outputDir.exists()) { + outputDir.mkdirs() + } + val zipFile = outputDir.resolve("assets-$arch.zip") + + // --- Part 1: Get the classes.jar from our llama-impl AAR --- + val llamaAarName = when (arch) { + "arm64-v8a" -> "llama-impl-v8-release.aar" + "armeabi-v7a" -> "llama-impl-v7-release.aar" + else -> throw IllegalArgumentException("Unsupported architecture for Llama AAR: $arch") + } + val originalLlamaAarFile = project.rootDir.resolve("llama-impl/build/outputs/aar/$llamaAarName") + + val tempDir = project.layout.buildDirectory.dir("tmp/d8/$arch").get().asFile + tempDir.deleteRecursively() + tempDir.mkdirs() + val tempClassesJar = File(tempDir, "classes.jar") + + // Extract just the classes.jar from our target AAR + ZipInputStream(originalLlamaAarFile.inputStream()).use { zis -> + var entry = zis.nextEntry + while (entry != null) { + if (entry.name == "classes.jar") { + tempClassesJar.outputStream().use { fos -> zis.copyTo(fos) } + break + } + entry = zis.nextEntry + } + } + if (!tempClassesJar.exists()) { + throw GradleException("classes.jar not found inside ${originalLlamaAarFile.name}") + } - val zipFile = outputDir.resolve("assets-$arch.zip") - val sourceDir = project.rootDir.resolve("assets") - val bootstrapName = "bootstrap-$arch.zip" - val androidSdkName = "android-sdk-$arch.zip" - - ZipOutputStream(zipFile.outputStream()).use { zipOut -> - arrayOf( - androidSdkName, - "localMvnRepository.zip", - "gradle-8.14.3-bin.zip", - "gradle-api-8.14.3.jar.zip", - "documentation.db", - bootstrapName, - ).forEach { fileName -> + val llamaImplProject = project.project(":llama-impl") + val flavorName = if (arch == "arm64-v8a") "v8" else "v7" + val configName = "${flavorName}ReleaseRuntimeClasspath" + val runtimeClasspathFiles = llamaImplProject.configurations.getByName(configName).files + + val explodedAarsDir = project.layout.buildDirectory.dir("tmp/exploded-aars/$arch").get().asFile + explodedAarsDir.mkdirs() + + val d8Classpath = mutableListOf() + runtimeClasspathFiles.forEach { file -> + if (file.name.endsWith(".jar")) { + d8Classpath.add(file) + } else if (file.name.endsWith(".aar")) { + // It's an AAR, extract its classes.jar + project.copy { + from(project.zipTree(file)) { + include("classes.jar") + } + into(explodedAarsDir) + // Rename to avoid collisions + rename { "${file.nameWithoutExtension}-classes.jar" } + } + d8Classpath.add(File(explodedAarsDir, "${file.nameWithoutExtension}-classes.jar")) + } + } + + // --- Part 3: Run D8 with the corrected command-line arguments --- + val dexOutputFile = File(tempDir, "classes.dex") + project.exec { + val androidSdkDir = android.sdkDirectory.absolutePath + val buildToolsVersion = android.buildToolsVersion + val d8Executable = File("$androidSdkDir/build-tools/$buildToolsVersion/d8") + + // 1. Start building the command arguments list + val d8Command = mutableListOf() + d8Command.add(d8Executable.absolutePath) + d8Command.add("--release") + d8Command.add("--min-api") + d8Command.add(android.defaultConfig.minSdk.toString()) // Add minSdk for better desugaring + + // 2. Add the --classpath flag REPEATEDLY for each dependency file + d8Classpath.forEach { file -> + if (file.exists()) { + d8Command.add("--classpath") + d8Command.add(file.absolutePath) + } + } + + // 3. Add the final output and input arguments + d8Command.add("--output") + d8Command.add(tempDir.absolutePath) + d8Command.add(tempClassesJar.absolutePath) + + // 4. Set the full command line + commandLine(d8Command) + }.assertNormalExitValue() + + if (!dexOutputFile.exists()) { + throw GradleException("D8 task failed to produce classes.dex") + } + + // --- Part 4: Repackage everything into the final assets-*.zip (Unchanged) --- + val sourceDir = project.rootDir.resolve("assets") + val bootstrapName = "bootstrap-$arch.zip" + val androidSdkName = "android-sdk-$arch.zip" + ZipOutputStream(zipFile.outputStream()).use { zipOut -> + arrayOf( + androidSdkName, + "localMvnRepository.zip", + "gradle-8.14.3-bin.zip", + "gradle-api-8.14.3.jar.zip", + "documentation.db", + bootstrapName, + ).forEach { fileName -> val filePath = sourceDir.resolve(fileName) if (!filePath.exists()) { throw FileNotFoundException(filePath.absolutePath) @@ -381,18 +500,126 @@ fun createAssetsZip(arch: String) { filePath.inputStream().use { input -> input.copyTo(zipOut) } zipOut.closeEntry() } + project.logger.lifecycle("Repackaging Llama AAR with classes.dex...") + + // Create the entry for our modified AAR inside assets-*.zip + zipOut.putNextEntry(ZipEntry("dynamic_libs/llama.aar")) + + // Use another ZipOutputStream to build the new AAR in memory and stream it + ZipOutputStream(zipOut).use { aarZipOut -> + // Copy all files from the original AAR *except* classes.jar + ZipInputStream(originalLlamaAarFile.inputStream()).use { originalAarStream -> + var entry = originalAarStream.nextEntry + while (entry != null) { + if (entry.name != "classes.jar") { + aarZipOut.putNextEntry(ZipEntry(entry.name)) + originalAarStream.copyTo(aarZipOut) + aarZipOut.closeEntry() + } + entry = originalAarStream.nextEntry + } + } + aarZipOut.putNextEntry(ZipEntry("classes.dex")) + dexOutputFile.inputStream().use { dexInput -> dexInput.copyTo(aarZipOut) } + aarZipOut.closeEntry() + } + println("Created ${zipFile.name} successfully at ${zipFile.parentFile.absolutePath}") + } +} - println("Created ${zipFile.name} successfully at ${zipFile.parentFile.absolutePath}") - } +fun registerBundleLlamaAssetsTask(flavor: String, arch: String): TaskProvider { + val capitalized = + flavor.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() } + return tasks.register("bundle${capitalized}LlamaAssets") { + dependsOn("assemble${capitalized}Assets") + + doLast { + val assetsZip = + project.layout.buildDirectory + .file("outputs/assets/assets-$arch.zip") + .get() + .asFile + if (!assetsZip.exists()) { + throw GradleException("Assets zip not found: ${assetsZip.absolutePath}. Run assemble${capitalized}Assets first.") + } + + val tempAar = Files.createTempFile("llama-$flavor", ".aar").toFile() + var found = false + ZipInputStream(assetsZip.inputStream()).use { zis -> + var entry = zis.nextEntry + while (entry != null) { + if (entry.name == "dynamic_libs/llama.aar") { + tempAar.outputStream().use { zis.copyTo(it) } + found = true + break + } + entry = zis.nextEntry + } + } + + if (!found) { + tempAar.delete() + throw GradleException("dynamic_libs/llama.aar not found inside ${assetsZip.name}") + } + + val targetDir = project.rootProject.file("assets/release/$flavor/dynamic_libs") + targetDir.mkdirs() + val destBr = File(targetDir, "llama-$flavor.aar.br") + val destAar = File(targetDir, "llama-$flavor.aar") + + destBr.delete() + destAar.delete() + + val brotliAvailable = + try { + val result = + project.exec { + commandLine("brotli", "--version") + isIgnoreExitValue = true + } + result.exitValue == 0 + } catch (_: Exception) { + false + } + + if (brotliAvailable) { + project.exec { + commandLine("brotli", "-f", "-o", destBr.absolutePath, tempAar.absolutePath) + } + project.logger.lifecycle( + "Bundled llama AAR compressed to ${ + destBr.relativeTo( + project.rootProject.projectDir + ) + }" + ) + destAar.delete() + } else { + project.logger.warn( + "brotli CLI not found; bundling llama AAR uncompressed at ${ + destAar.relativeTo( + project.rootProject.projectDir + ) + }" + ) + tempAar.copyTo(destAar, overwrite = true) + destBr.delete() + } + + tempAar.delete() + } + } } tasks.register("assembleV8Assets") { + dependsOn(":llama-impl:assembleV8Release") doLast { createAssetsZip("arm64-v8a") } } tasks.register("assembleV7Assets") { + dependsOn(":llama-impl:assembleV7Release") doLast { createAssetsZip("armeabi-v7a") } @@ -402,6 +629,9 @@ tasks.register("assembleAssets") { dependsOn("assembleV8Assets", "assembleV7Assets") } +val bundleLlamaV7Assets = registerBundleLlamaAssetsTask(flavor = "v7", arch = "armeabi-v7a") +val bundleLlamaV8Assets = registerBundleLlamaAssetsTask(flavor = "v8", arch = "arm64-v8a") + tasks.register("recompressApk") { doLast { val abi: String = extensions.extraProperties["abi"].toString() @@ -437,6 +667,8 @@ afterEvaluate { extensions.extraProperties["noCompressExtensions"] = noCompress } } + + dependsOn(bundleLlamaV8Assets) } tasks.named("assembleV7Release").configure { @@ -449,6 +681,8 @@ afterEvaluate { extensions.extraProperties["noCompressExtensions"] = noCompress } } + + dependsOn(bundleLlamaV7Assets) } tasks.named("assembleV8Debug").configure { diff --git a/app/src/main/java/com/itsaky/androidide/agent/fragments/AiSettingsFragment.kt b/app/src/main/java/com/itsaky/androidide/agent/fragments/AiSettingsFragment.kt index 513253c139..a56a17da3a 100644 --- a/app/src/main/java/com/itsaky/androidide/agent/fragments/AiSettingsFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/agent/fragments/AiSettingsFragment.kt @@ -21,6 +21,7 @@ import com.google.android.material.textfield.TextInputLayout import com.itsaky.androidide.R import com.itsaky.androidide.agent.repository.AiBackend import com.itsaky.androidide.agent.viewmodel.AiSettingsViewModel +import com.itsaky.androidide.agent.viewmodel.EngineState import com.itsaky.androidide.agent.viewmodel.ModelLoadingState import com.itsaky.androidide.databinding.FragmentAiSettingsBinding import com.itsaky.androidide.utils.flashInfo @@ -45,11 +46,7 @@ class AiSettingsFragment : Fragment(R.layout.fragment_ai_settings) { requireContext().contentResolver.takePersistableUriPermission(it, takeFlags) val uriString = it.toString() - // The fragment's only job is to save the path via the ViewModel. - viewModel.saveLocalModelPath(uriString) viewModel.loadModelFromUri(uriString, requireContext()) - // It also updates its own UI. - updateLocalLlmUi(binding.backendSpecificSettingsContainer) flashInfo("Attempting to load selected model...") } } @@ -84,7 +81,6 @@ class AiSettingsFragment : Fragment(R.layout.fragment_ai_settings) { binding.backendAutocomplete.setOnItemClickListener { _, _, position, _ -> val selectedBackend = backends[position] - // Its only job is to save the backend selection. viewModel.saveBackend(selectedBackend) updateBackendSpecificUi(selectedBackend) } @@ -100,7 +96,6 @@ class AiSettingsFragment : Fragment(R.layout.fragment_ai_settings) { .inflate(R.layout.layout_settings_local_llm, container, true) updateLocalLlmUi(localLlmView) } - AiBackend.GEMINI -> { val geminiApiView = LayoutInflater.from(requireContext()) .inflate(R.layout.layout_settings_gemini_api, container, true) @@ -114,8 +109,7 @@ class AiSettingsFragment : Fragment(R.layout.fragment_ai_settings) { val browseButton = view.findViewById