2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rust + TauriでADBツールを作る——APKインストール・アプリ一覧・クリップボード同期の実装

2
Posted at

8年前のMacBook Air(Intel)で全テスト済み。7本のMacアプリをソロ開発した実体験から書いています。案件でも広告でもありません。

HiyokoKitにはAPKインストールとAndroidアプリマネージャーが含まれている。どちらも内部でADBを使っている。実装を紹介する。


APKインストール

#[tauri::command]
async fn install_apk(apk_path: String) -> Result<String, AppError> {
    let output = tokio::process::Command::new("adb")
        .args(["install", "-r", &apk_path]) // -r = 既存アプリの上書きインストール
        .output()
        .await?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    if stdout.contains("Success") {
        Ok("インストール成功".into())
    } else {
        let error = if stderr.contains("INSTALL_FAILED_VERSION_DOWNGRADE") {
            "新しいバージョンの上に古いバージョンはインストールできません。ダウングレードは -d フラグを使用してください。"
        } else if stderr.contains("INSTALL_FAILED_ALREADY_EXISTS") {
            "アプリは既にインストールされています。再インストールオプションを使用してください。"
        } else {
            "インストールに失敗しました"
        };
        Err(AppError::Adb(error.into()))
    }
}

エラーコードを個別にパースする——「インストール失敗」という汎用メッセージはユーザーの役に立たない。


アプリ一覧取得

#[derive(Serialize)]
pub struct AppInfo {
    package_name: String,
    is_system: bool,
}

#[tauri::command]
async fn list_apps(include_system: bool) -> Result<Vec<AppInfo>, AppError> {
    let flag = if include_system { "-l" } else { "-3" }; // -3 = サードパーティのみ

    let output = tokio::process::Command::new("adb")
        .args(["shell", "pm", "list", "packages", flag])
        .output()
        .await?;

    let apps = String::from_utf8_lossy(&output.stdout)
        .lines()
        .filter_map(|line| {
            line.strip_prefix("package:").map(|pkg| AppInfo {
                package_name: pkg.trim().to_string(),
                is_system: !include_system,
            })
        })
        .collect();

    Ok(apps)
}

アプリのアンインストール

#[tauri::command]
async fn uninstall_app(package_name: String) -> Result<(), AppError> {
    let output = tokio::process::Command::new("adb")
        .args(["uninstall", &package_name])
        .output()
        .await?;

    if String::from_utf8_lossy(&output.stdout).contains("Success") {
        Ok(())
    } else {
        Err(AppError::Adb(format!("{} のアンインストールに失敗しました", package_name)))
    }
}

AndroidとMacのクリップボード同期

#[tauri::command]
async fn push_clipboard_to_android(text: String) -> Result<(), AppError> {
    tokio::process::Command::new("adb")
        .args(["shell", "am", "broadcast", "-a", "clipper.set", "-e", "text", &text])
        .status()
        .await?;
    Ok(())
}

#[tauri::command]
async fn get_android_clipboard() -> Result<String, AppError> {
    let output = tokio::process::Command::new("adb")
        .args(["shell", "am", "broadcast", "-a", "clipper.get"])
        .output()
        .await?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    extract_clipboard_from_broadcast(&stdout)
}

注意:ADB経由のクリップボード同期にはAndroidデバイス側にClipperなどのアプリが必要。


ADBが見つからない場合のエラーハンドリング

fn check_adb_available() -> Result<(), AppError> {
    Command::new("adb")
        .arg("version")
        .output()
        .map_err(|_| AppError::Adb(
            "ADBが見つかりません。Android Platform Toolsをインストールしてください。".into()
        ))?;
    Ok(())
}

初回使用時ではなく、起動時にチェックする。ADBがない場合はすぐにユーザーに伝えるべきだ。


記事が参考になったら ❤️ もらえると励みになります!

HiyokoKit | X → @hiyoyok

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?