Files
MosisService/DEV_PORTAL_M10_DEVICE.md

16 KiB

Milestone 10: Device-Side App Management

Status: Planning Goal: Install, update, and manage apps on Mosis devices.


Overview

Device-side app management handles the full lifecycle of apps on user devices: discovery, installation, updates, and removal.


Components

┌─────────────────────────────────────────────────────────────┐
│                     MosisService                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐   │
│  │  AppManager   │  │ UpdateService │  │  AppStore UI  │   │
│  │  (C++ class)  │  │  (Background) │  │ (System App)  │   │
│  └───────────────┘  └───────────────┘  └───────────────┘   │
│         │                   │                   │           │
│         └───────────────────┼───────────────────┘           │
│                             │                               │
│                    ┌────────┴────────┐                      │
│                    │ LuaSandboxManager│                     │
│                    └─────────────────┘                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Storage Layout

/data/mosis/
├── config/
│   ├── device.json         # Device ID, settings
│   └── apps.json           # Installed apps registry
├── apps/
│   └── {package_id}/
│       ├── package/        # Extracted app files
│       │   ├── manifest.json
│       │   └── assets/
│       ├── data/           # App data (VirtualFS)
│       ├── cache/          # App cache
│       └── db/             # SQLite databases
├── downloads/              # Temp download location
└── backups/                # App data backups (before update)

AppManager Class

Interface

namespace mosis {

struct InstalledApp {
    std::string package_id;
    std::string name;
    std::string version_name;
    int version_code;
    std::string install_path;
    std::vector<std::string> permissions;
    std::chrono::system_clock::time_point installed_at;
    std::chrono::system_clock::time_point updated_at;
    int64_t package_size;
    int64_t data_size;
};

struct InstallProgress {
    enum class Stage {
        Downloading,
        Verifying,
        Extracting,
        Registering,
        Complete,
        Failed
    };
    Stage stage;
    float progress;  // 0.0 - 1.0
    std::string error;
};

using ProgressCallback = std::function<void(const InstallProgress&)>;

class AppManager {
public:
    explicit AppManager(const std::string& data_root);
    ~AppManager();

    // Installation
    bool Install(const std::string& package_url,
                 const std::string& signature,
                 ProgressCallback callback);
    bool InstallFromFile(const std::string& package_path,
                         ProgressCallback callback);

    // Uninstallation
    bool Uninstall(const std::string& package_id, bool keep_data = false);

    // Updates
    bool Update(const std::string& package_id,
                const std::string& package_url,
                const std::string& signature,
                ProgressCallback callback);

    // Queries
    std::vector<InstalledApp> GetInstalledApps() const;
    std::optional<InstalledApp> GetApp(const std::string& package_id) const;
    bool IsInstalled(const std::string& package_id) const;

    // Data management
    int64_t GetAppDataSize(const std::string& package_id) const;
    bool ClearAppData(const std::string& package_id);
    bool ClearAppCache(const std::string& package_id);
    bool BackupAppData(const std::string& package_id);
    bool RestoreAppData(const std::string& package_id);

    // Integration with sandbox
    void SetSandboxManager(LuaSandboxManager* manager);

private:
    std::string m_data_root;
    LuaSandboxManager* m_sandbox_manager = nullptr;
    mutable std::mutex m_mutex;
    std::map<std::string, InstalledApp> m_installed_apps;

    bool VerifyPackage(const std::string& path, const std::string& signature);
    bool ExtractPackage(const std::string& path, const std::string& dest);
    void LoadInstalledApps();
    void SaveInstalledApps();
};

} // namespace mosis

Implementation

bool AppManager::Install(const std::string& package_url,
                         const std::string& signature,
                         ProgressCallback callback) {
    callback({InstallProgress::Stage::Downloading, 0.0f, ""});

    // Download package
    std::string download_path = m_data_root + "/downloads/" + GenerateUUID();
    if (!DownloadFile(package_url, download_path, [&](float p) {
        callback({InstallProgress::Stage::Downloading, p, ""});
    })) {
        callback({InstallProgress::Stage::Failed, 0.0f, "Download failed"});
        return false;
    }

    callback({InstallProgress::Stage::Verifying, 0.0f, ""});

    // Verify signature
    if (!VerifyPackage(download_path, signature)) {
        std::filesystem::remove(download_path);
        callback({InstallProgress::Stage::Failed, 0.0f, "Signature verification failed"});
        return false;
    }

    // Extract manifest to get package_id
    auto manifest = ExtractManifest(download_path);
    if (!manifest) {
        std::filesystem::remove(download_path);
        callback({InstallProgress::Stage::Failed, 0.0f, "Invalid manifest"});
        return false;
    }

    callback({InstallProgress::Stage::Extracting, 0.0f, ""});

    // Check if already installed (update path)
    std::string install_path = m_data_root + "/apps/" + manifest->package_id;
    if (std::filesystem::exists(install_path + "/package")) {
        // Backup existing data
        BackupAppData(manifest->package_id);
        // Remove old package
        std::filesystem::remove_all(install_path + "/package");
    }

    // Extract package
    std::filesystem::create_directories(install_path + "/package");
    if (!ExtractPackage(download_path, install_path + "/package")) {
        callback({InstallProgress::Stage::Failed, 0.0f, "Extraction failed"});
        return false;
    }

    // Clean up download
    std::filesystem::remove(download_path);

    callback({InstallProgress::Stage::Registering, 0.0f, ""});

    // Create data directories
    std::filesystem::create_directories(install_path + "/data");
    std::filesystem::create_directories(install_path + "/cache");
    std::filesystem::create_directories(install_path + "/db");

    // Register app
    InstalledApp app{
        .package_id = manifest->package_id,
        .name = manifest->name,
        .version_name = manifest->version,
        .version_code = manifest->version_code,
        .install_path = install_path,
        .permissions = manifest->permissions,
        .installed_at = std::chrono::system_clock::now(),
        .updated_at = std::chrono::system_clock::now(),
        .package_size = std::filesystem::file_size(download_path)
    };

    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_installed_apps[manifest->package_id] = app;
        SaveInstalledApps();
    }

    callback({InstallProgress::Stage::Complete, 1.0f, ""});
    return true;
}

bool AppManager::Uninstall(const std::string& package_id, bool keep_data) {
    std::lock_guard<std::mutex> lock(m_mutex);

    auto it = m_installed_apps.find(package_id);
    if (it == m_installed_apps.end()) {
        return false;
    }

    // Stop app if running
    if (m_sandbox_manager && m_sandbox_manager->IsAppRunning(package_id)) {
        m_sandbox_manager->StopApp(package_id);
    }

    // Remove files
    std::string install_path = it->second.install_path;
    std::filesystem::remove_all(install_path + "/package");
    std::filesystem::remove_all(install_path + "/cache");

    if (!keep_data) {
        std::filesystem::remove_all(install_path + "/data");
        std::filesystem::remove_all(install_path + "/db");
        std::filesystem::remove_all(install_path);
    }

    // Unregister
    m_installed_apps.erase(it);
    SaveInstalledApps();

    return true;
}

Update Service

Background Update Checker

class UpdateService {
public:
    UpdateService(AppManager* app_manager, const std::string& api_base);

    // Start background checking
    void Start(std::chrono::hours interval = std::chrono::hours(24));
    void Stop();

    // Manual check
    std::vector<UpdateInfo> CheckForUpdates();

    // Download and install update
    bool ApplyUpdate(const std::string& package_id, ProgressCallback callback);

    // Settings
    void SetAutoUpdate(bool enabled);
    void SetWifiOnly(bool wifi_only);

private:
    void CheckLoop();

    AppManager* m_app_manager;
    std::string m_api_base;
    std::thread m_check_thread;
    std::atomic<bool> m_running{false};
    bool m_auto_update = false;
    bool m_wifi_only = true;
};

Update Check Flow

1. Get list of installed apps
2. Call API: GET /store/apps/updates?packages=com.a,com.b,com.c
3. API returns list of available updates
4. If auto-update enabled and on WiFi:
   - Download and install in background
   - Notify user of completed updates
5. If manual:
   - Show notification with update count
   - User opens App Store to review

App Store System App

UI Screens

Home
├── Featured apps
├── Categories
├── Search
└── My Apps
    ├── Installed
    ├── Updates available
    └── Previously installed

App Detail
├── Icon, name, developer
├── Screenshots
├── Description
├── Permissions
├── Reviews (future)
└── [Install] / [Update] / [Open]

Settings
├── Auto-update (WiFi only)
├── Storage usage
└── Clear all caches

RML Structure

<!-- app_store/main.rml -->
<rml>
<head>
    <link type="text/rcss" href="../../ui/theme.rcss"/>
    <link type="text/rcss" href="store.rcss"/>
    <script src="store.lua"/>
</head>
<body>
    <div class="app-bar">
        <h1>App Store</h1>
        <div class="search-icon" onclick="showSearch()"/>
    </div>

    <div id="content">
        <!-- Dynamic content loaded by Lua -->
    </div>

    <div class="bottom-nav">
        <div class="nav-item active" onclick="showHome()">
            <img src="icons/home.tga"/>
            <span>Home</span>
        </div>
        <div class="nav-item" onclick="showCategories()">
            <img src="icons/category.tga"/>
            <span>Categories</span>
        </div>
        <div class="nav-item" onclick="showMyApps()">
            <img src="icons/apps.tga"/>
            <span>My Apps</span>
        </div>
    </div>
</body>
</rml>

Lua Store Logic

-- store.lua
local api = require("store_api")
local ui = require("ui")

local state = {
    screen = "home",
    featured = {},
    categories = {},
    installed = {},
    updates = {}
}

function init()
    -- Load installed apps
    state.installed = mosis.apps.getInstalled()

    -- Fetch featured apps
    api.getFeatured(function(apps)
        state.featured = apps
        render()
    end)

    -- Check for updates
    checkUpdates()
end

function checkUpdates()
    local package_ids = {}
    for _, app in ipairs(state.installed) do
        table.insert(package_ids, app.package_id)
    end

    api.checkUpdates(package_ids, function(updates)
        state.updates = updates
        render()
    end)
end

function installApp(package_id)
    local app = findApp(package_id)
    if not app then return end

    showProgress(app.name)

    mosis.apps.install(app.download_url, app.signature, function(progress)
        updateProgress(progress.stage, progress.progress)

        if progress.stage == "complete" then
            hideProgress()
            showToast(app.name .. " installed")
            state.installed = mosis.apps.getInstalled()
            render()
        elseif progress.stage == "failed" then
            hideProgress()
            showError("Installation failed: " .. progress.error)
        end
    end)
end

function openApp(package_id)
    mosis.apps.launch(package_id)
end

function uninstallApp(package_id)
    showConfirm("Uninstall " .. getAppName(package_id) .. "?", function(confirmed)
        if confirmed then
            mosis.apps.uninstall(package_id)
            state.installed = mosis.apps.getInstalled()
            render()
        end
    end)
end

Lua API for Apps

Exposed to System Apps

-- mosis.apps namespace (system apps only)

-- Get installed apps
local apps = mosis.apps.getInstalled()
-- Returns: [{package_id, name, version_name, version_code, installed_at}]

-- Install from store
mosis.apps.install(url, signature, callback)
-- callback(progress): {stage, progress, error}

-- Uninstall
mosis.apps.uninstall(package_id)

-- Launch app
mosis.apps.launch(package_id)

-- Get app info
local info = mosis.apps.getInfo(package_id)

-- Storage management
local size = mosis.apps.getDataSize(package_id)
mosis.apps.clearCache(package_id)
mosis.apps.clearData(package_id)

Exposed to All Apps

-- mosis.app namespace (current app only)

-- Get own package info
local info = mosis.app.info()
-- Returns: {package_id, name, version_name, version_code}

-- Check for update
mosis.app.checkUpdate(function(available, new_version)
    if available then
        showUpdatePrompt(new_version)
    end
end)

-- Open store page for self
mosis.app.openStorePage()

Permissions for App Management

-- Required permission to use mosis.apps.*
permissions = {"system.app_management"}

-- Only granted to:
-- - App Store system app
-- - Settings system app
-- - Other OEM system apps

Installation Intents

mosis://store/app/com.developer.myapp
mosis://store/install?url=...&sig=...

From App Store

-- User taps Install button
installApp("com.developer.myapp")

From ADB (Development)

adb shell am broadcast -a com.omixlab.mosis.INSTALL_APP \
    --es package_path "/sdcard/myapp.mosis"

Security

Package Verification

  1. Verify ZIP integrity
  2. Verify Ed25519 signature
  3. Verify signer is registered developer
  4. Verify app not in blocklist
  5. Verify permissions are declared

Installation Sources

Source Allowed
Official store Always
Developer sideload If enabled in settings
Unknown APK Never (MosisService only)

Sandboxing

  • All apps run in LuaSandbox
  • File access limited to app's data directory
  • Network access requires permission
  • Hardware access requires permission + user gesture

Deliverables

  • AppManager C++ class
  • UpdateService background checker
  • App Store system app
  • Lua API bindings (mosis.apps, mosis.app)
  • Installation progress UI
  • Uninstall confirmation UI
  • Storage management UI
  • Deep link handling

Open Questions

  1. App backup to cloud?
  2. Family sharing / multiple devices?
  3. Enterprise MDM integration?
  4. Sideloading policy?

References