機能と API

Android 17 では、デベロッパー向けに優れた新しい機能と API が導入されました。以下のセクションでは、これらの機能の概要を説明し、関連する API を使い始めるうえで役立つ情報を提供します。

新しい API、変更された API、削除された API の一覧については、API 差分レポートをご覧ください。新しい API について詳しくは、Android API リファレンスをご覧ください。新しい API は、見つけやすいようにハイライト表示されています。

また、プラットフォームの変更がアプリに影響する可能性がある領域も確認する必要があります。詳細については、次のページをご覧ください。

コア機能

Android 17 では、Android のコア機能に関連する次の新機能が追加されています。

新しい ProfilingManager トリガー

Android 17 では、パフォーマンスの問題をデバッグするための詳細なデータを収集するのに役立つ、いくつかの新しいシステム トリガーが ProfilingManager に追加されています。

新しいトリガーは次のとおりです。

  • TRIGGER_TYPE_COLD_START: アプリのコールド スタート中にトリガーが発生します。レスポンスでコールスタックのサンプルとシステム トレースの両方が提供されます。
  • TRIGGER_TYPE_OOM: アプリが OutOfMemoryError をスローし、それに応じて Java ヒープダンプを提供したときにトリガーが発生します。
  • TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE: 異常な CPU 使用率が原因でアプリが強制終了されたときにトリガーが発生し、レスポンスとしてコールスタックのサンプルが提供されます。

システム トリガーの設定方法については、トリガーベースのプロファイリングプロファイリング データの取得と分析に関するドキュメントをご覧ください。

JobDebugInfo API

Android 17 では、デベロッパーが JobScheduler ジョブのデバッグに役立つ新しい JobDebugInfo API が導入されました。ジョブが実行されない理由、実行時間、その他の集計情報などを確認できます。

拡張された JobDebugInfo API の最初のメソッドは getPendingJobReasonStats() です。これは、ジョブが実行待ち状態だった理由と、それぞれの累積待ち時間を示すマップを返します。このメソッドは、getPendingJobReasonsHistory() メソッドと getPendingJobReasons() メソッドを結合して、スケジュールされたジョブが想定どおりに実行されない理由を把握できるようにします。また、期間とジョブの理由の両方を 1 つのメソッドで使用できるようにすることで、情報取得を簡素化します。

たとえば、指定された jobId に対して、メソッドは PENDING_JOB_REASON_CONSTRAINT_CHARGING と 60000 ミリ秒の期間を返すことがあります。これは、充電の制約が満たされなかったため、ジョブが 60000 ミリ秒間保留されたことを示します。

プライバシー

Android 17 には、ユーザーのプライバシーを強化するための次の新機能が含まれています。

Android の連絡先選択ツール

Android の連絡先ピッカーは、ユーザーがアプリと連絡先を共有するための標準化された閲覧可能なインターフェースです。Android 17(API レベル 37)以上を搭載したデバイスで利用できるこのピッカーは、広範な READ_CONTACTS 権限に代わるプライバシー保護の選択肢を提供します。ユーザーのアドレス帳全体へのアクセスをリクエストする代わりに、アプリは必要なデータ フィールド(電話番号やメールアドレスなど)を指定し、ユーザーは共有する特定の連絡先を選択します。これにより、アプリは選択したデータのみに読み取りアクセスできるようになり、きめ細かい制御が可能になります。また、UI を構築または維持することなく、組み込みの検索、プロファイルの切り替え、複数選択機能を使用して、一貫したユーザー エクスペリエンスを提供できます。

詳しくは、連絡先選択ツールのドキュメントをご覧ください。

セキュリティ

Android 17 では、デバイスとアプリのセキュリティを強化するために、以下の新機能が追加されています。

Android の高度な保護機能モード(AAPM)

Android の高度な保護モードは、Android ユーザーに強力な新しいセキュリティ機能を提供し、ユーザー(特にリスクの高いユーザー)を高度な攻撃から保護するうえで大きな進歩となります。オプトイン機能として設計された AAPM は、ユーザーがいつでもオンにできる単一の構成設定で有効になり、セキュリティ保護の意見の強いセットが適用されます。

これらのコア構成には、提供元不明のアプリのインストール(サイドローディング)のブロック、USB データ シグナリングの制限、Google Play プロテクトのスキャン義務付けが含まれており、デバイスの攻撃対象領域を大幅に削減します。デベロッパーは AdvancedProtectionManager API を使用してこの機能と統合し、モードのステータスを検出できます。これにより、ユーザーがオプトインしたときに、アプリケーションが自動的にセキュリティ強化された状態に移行したり、リスクの高い機能を制限したりできるようになります。

PQC APK 署名

Android は、量子コンピューティングを利用した攻撃の潜在的な脅威からアプリの署名 ID を保護するために、ハイブリッド APK 署名スキームをサポートするようになりました。この機能では、新しい APK 署名スキームが導入され、従来の署名鍵(RSA や EC など)と新しい耐量子暗号(PQC)アルゴリズム(ML-DSA)をペアにすることができます。

このハイブリッド アプローチにより、従来の署名検証に依存する古い Android バージョンやデバイスとの完全な下位互換性を維持しながら、将来の量子攻撃からアプリを保護できます。

デベロッパーへの影響

  • Play アプリ署名を使用するアプリ: Play アプリ署名を使用している場合は、Google Play で生成された PQC 鍵を使用してハイブリッド署名をアップグレードするオプションが Google Play に表示されるまで待つことができます。これにより、鍵を手動で管理しなくてもアプリを保護できます。
  • 自己管理鍵を使用するアプリ: 署名鍵を自分で管理しているデベロッパーは、更新された Android ビルドツール(apksigner など)を使用して、PQC 鍵と新しい従来の鍵を組み合わせたハイブリッド ID にローテーションできます (新しい従来の鍵を作成する必要があります。古い鍵を再利用することはできません)。

接続

Android 17 では、デバイスとアプリの接続性を向上させるために次の機能が追加されています。

制約のある衛星ネットワーク

低帯域幅の衛星ネットワークでアプリが効果的に機能するように最適化を実装します。

ユーザー エクスペリエンスとシステム UI

Android 17 には、ユーザー エクスペリエンスを改善するための以下の変更が含まれています。

アシスタント専用の音量ストリーム

テスト可能ですか?(必須ビルド) はい (Android 17 ベータ版 1 以降)
targetSDKVersion の変更が必要ですか?(API レベル) いいえ(該当なし)
compileSDKVersion の変更が必要ですか?(API レベル) いいえ(該当なし)

Android 17 では、USAGE_ASSISTANT での再生用に、アシスタント アプリ専用のアシスタント音量ストリームが導入されました。この変更により、アシスタントの音声が標準のメディア ストリームから切り離され、ユーザーは両方の音量を個別に制御できるようになります。これにより、アシスタントの回答の可聴性を維持しながらメディア再生をミュートするなどのシナリオが可能になります。

新しい MODE_ASSISTANT_CONVERSATION 音声モードにアクセスできるアシスタント アプリは、音量調節の一貫性をさらに改善できます。アシスタント アプリはこのモードを使用して、アクティブなアシスタント セッションに関するヒントをシステムに提供し、アクティブな USAGE_ASSISTANT 再生の外側で、または接続された Bluetooth 周辺機器でアシスタント ストリームを制御できるようにします。

ハンドオフ

ハンドオフは、Android 17 で導入される新しい機能と API です。アプリ デベロッパーはこれを統合して、ユーザーにデバイス間の継続性を提供できます。これにより、ユーザーは 1 つの Android デバイスでアプリ アクティビティを開始し、別の Android デバイスに移行できます。ハンドオフはユーザーのデバイスのバックグラウンドで実行され、受信側のデバイスのランチャーやタスクバーなどのさまざまなエントリ ポイントを通じて、ユーザーの近くにある他のデバイスで利用可能なアクティビティを表示します。

アプリは、受信側のデバイスに同じネイティブ Android アプリがインストールされていて利用可能な場合、ハンドオフでそのアプリを起動するように指定できます。このアプリ間フローでは、ユーザーは指定されたアクティビティにディープリンクされます。また、アプリからウェブへのハンドオフをフォールバック オプションとして提供したり、URL ハンドオフで直接実装したりすることもできます。

ハンドオフのサポートはアクティビティ単位で実装されます。ハンドオフを有効にするには、アクティビティの setHandoffEnabled() メソッドを呼び出します。受け渡しとともに追加のデータを渡す必要がある場合もあります。これにより、受信デバイスで再作成されたアクティビティが適切な状態を復元できます。onHandoffActivityRequested() コールバックを実装して、受け取り側のデバイスでハンドオフがアクティビティを処理して再作成する方法を指定する詳細を含む HandoffActivityData オブジェクトを返します。

ライブ アップデート - セマンティック カラー API

Android 17 では、ライブ アップデートを通じて、普遍的な意味を持つ色を サポートするセマンティック カラー API がリリースされます。

次のクラスはセマンティック カラーをサポートしています。

色の意味

  • : 安全に関連付けられています。 安全な状況であることをユーザーに知らせる場合に使用します。
  • オレンジ: 注意を促し、物理的な危険をマークするために使用します。この色は、ユーザーが保護設定を適切に行うために注意を払う必要がある状況で 使用してください。
  • : 一般的に危険、停止を示します。緊急にユーザーの注意を引く必要がある場合に表示します。
  • : 情報提供を目的とし、他のコンテンツと区別する必要があるコンテンツに使用するニュートラルな色です。

次の例は、通知のテキストにセマンティック スタイルを適用する方法を示しています。

  val ssb = SpannableStringBuilder()
        .append("Colors: ")
        .append("NONE", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_UNSPECIFIED), 0)
        .append(", ")
        .append("INFO", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_INFO), 0)
        .append(", ")
        .append("SAFE", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_SAFE), 0)
        .append(", ")
        .append("CAUTION", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_CAUTION), 0)
        .append(", ")
        .append("DANGER", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_DANGER), 0)

    Notification.Builder(context, channelId)
          .setSmallIcon(R.drawable.ic_icon)
          .setContentTitle("Hello World!")
          .setContentText(ssb)
          .setOngoing(true)
              .setRequestPromotedOngoing(true)

Android 17 向け UWB ダウンリンク TDoA API

ダウンリンク到達時間差(DL-TDoA)測距により、デバイスは信号の相対到達時間を測定することで、複数のアンカーに対する相対位置を特定できます。

次のスニペットは、Ranging Manager を初期化し、デバイスの機能を検証して、DL-TDoA セッションを開始する方法を示しています。

Kotlin

class RangingApp {

    fun initDlTdoa(context: Context) {
        // Initialize the Ranging Manager
        val rangingManager = context.getSystemService(RangingManager::class.java)

        // Register for device capabilities
        val capabilitiesCallback = object : RangingManager.CapabilitiesCallback {
            override fun onRangingCapabilities(capabilities: RangingCapabilities) {
                // Make sure Dl-TDoA is supported before starting the session
                if (capabilities.uwbCapabilities != null && capabilities.uwbCapabilities!!.isDlTdoaSupported) {
                    startDlTDoASession(context)
                }
            }
        }
        rangingManager.registerCapabilitiesCallback(Executors.newSingleThreadExecutor(), capabilitiesCallback)
    }

    fun startDlTDoASession(context: Context) {

        // Initialize the Ranging Manager
        val rangingManager = context.getSystemService(RangingManager::class.java)

        // Create session and configure parameters
        val executor = Executors.newSingleThreadExecutor()
        val rangingSession = rangingManager.createRangingSession(executor, RangingSessionCallback())
        val rangingRoundIndexes = intArrayOf(0)
        val config: ByteArray = byteArrayOf() // OOB config data
        val params = DlTdoaRangingParams.createFromFiraConfigPacket(config, rangingRoundIndexes)

        val rangingDevice = RangingDevice.Builder().build()
        val rawTagDevice = RawRangingDevice.Builder()
            .setRangingDevice(rangingDevice)
            .setDlTdoaRangingParams(params)
            .build()

        val dtTagConfig = RawDtTagRangingConfig.Builder(rawTagDevice).build()

        val preference = RangingPreference.Builder(DEVICE_ROLE_DT_TAG, dtTagConfig)
            .setSessionConfig(SessionConfig.Builder().build())
            .build()

        // Start the ranging session
        rangingSession.start(preference)
    }
}

private class RangingSessionCallback : RangingSession.Callback {
    override fun onDlTdoaResults(peer: RangingDevice, measurement: DlTdoaMeasurement) {
        // Process measurement results here
    }
}

Java

public class RangingApp {

    public void initDlTdoa(Context context) {

        // Initialize the Ranging Manager
        RangingManager rangingManager = context.getSystemService(RangingManager.class);

        // Register for device capabilities
        RangingManager.CapabilitiesCallback capabilitiesCallback = new RangingManager.CapabilitiesCallback() {
            @Override
            public void onRangingCapabilities(RangingCapabilities capabilities) {
                // Make sure Dl-TDoA is supported before starting the session
                if (capabilities.getUwbCapabilities() != null && capabilities.getUwbCapabilities().isDlTdoaSupported) {
                    startDlTDoASession(context);
                }
            }
        };
        rangingManager.registerCapabilitiesCallback(Executors.newSingleThreadExecutor(), capabilitiesCallback);
    }

    public void startDlTDoASession(Context context) {
        RangingManager rangingManager = context.getSystemService(RangingManager.class);

        // Create session and configure parameters
        Executor executor = Executors.newSingleThreadExecutor();
        RangingSession rangingSession = rangingManager.createRangingSession(executor, new RangingSessionCallback());
        int[] rangingRoundIndexes = new int[] {0};
        byte[] config = new byte[0]; // OOB config data
        DlTdoaRangingParams params = DlTdoaRangingParams.createFromFiraConfigPacket(config, rangingRoundIndexes);

        RangingDevice rangingDevice = new RangingDevice.Builder().build();
        RawRangingDevice rawTagDevice = new RawRangingDevice.Builder()
                .setRangingDevice(rangingDevice)
                .setDlTdoaRangingParams(params)
                .build();

        RawDtTagRangingConfig dtTagConfig = new RawDtTagRangingConfig.Builder(rawTagDevice).build();

        RangingPreference preference = new RangingPreference.Builder(DEVICE_ROLE_DT_TAG, dtTagConfig)
                .setSessionConfig(new SessionConfig.Builder().build())
                .build();

        // Start the ranging session
        rangingSession.start(preference);
    }

    private static class RangingSessionCallback implements RangingSession.Callback {

        @Override
        public void onDlTdoaResults(RangingDevice peer, DlTdoaMeasurement measurement) {
            // Process measurement results here
        }
    }
}

アウトオブバンド(OOB)構成

次のスニペットは、Wi-Fi と BLE の DL-TDoA OOB 構成データの例を示しています。

Java

// Wifi Configuration
byte[] wifiConfig = {
    (byte) 0xDD, (byte) 0x2D, (byte) 0x5A, (byte) 0x18, (byte) 0xFF, // Header
    (byte) 0x5F, (byte) 0x19, // FiRa Sub-Element
    (byte) 0x02, (byte) 0x00, // Profile ID
    (byte) 0x06, (byte) 0x02, (byte) 0x20, (byte) 0x08, // MAC Address
    (byte) 0x14, (byte) 0x01, (byte) 0x0C, // Preamble Index
    (byte) 0x27, (byte) 0x02, (byte) 0x08, (byte) 0x07, // Vendor ID
    (byte) 0x28, (byte) 0x06, (byte) 0xCA, (byte) 0xC8, (byte) 0xA6, (byte) 0xF7, (byte) 0x6F, (byte) 0x08, // Static STS IV
    (byte) 0x08, (byte) 0x02, (byte) 0x60, (byte) 0x09, // Slot Duration
    (byte) 0x1B, (byte) 0x01, (byte) 0x0A, // Slots per RR
    (byte) 0x09, (byte) 0x04, (byte) 0xE8, (byte) 0x03, (byte) 0x00, (byte) 0x00, // Duration
    (byte) 0x9F, (byte) 0x04, (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01  // Session ID
};

// BLE Configuration
byte[] bleConfig = {
    (byte) 0x2D, (byte) 0x16, (byte) 0xF4, (byte) 0xFF, // Header
    (byte) 0x5F, (byte) 0x19, // FiRa Sub-Element
    (byte) 0x02, (byte) 0x00, // Profile ID
    (byte) 0x06, (byte) 0x02, (byte) 0x20, (byte) 0x08, // MAC Address
    (byte) 0x14, (byte) 0x01, (byte) 0x0C, // Preamble Index
    (byte) 0x27, (byte) 0x02, (byte) 0x08, (byte) 0x07, // Vendor ID
    (byte) 0x28, (byte) 0x06, (byte) 0xCA, (byte) 0xC8, (byte) 0xA6, (byte) 0xF7, (byte) 0x6F, (byte) 0x08, // Static STS IV
    (byte) 0x08, (byte) 0x02, (byte) 0x60, (byte) 0x09, // Slot Duration
    (byte) 0x1B, (byte) 0x01, (byte) 0x0A, // Slots per RR
    (byte) 0x09, (byte) 0x04, (byte) 0xE8, (byte) 0x03, (byte) 0x00, (byte) 0x00, // Duration
    (byte) 0x9F, (byte) 0x04, (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01  // Session ID
};

OOB 構成がないために使用できない場合や、OOB 構成にないデフォルト値を変更する必要がある場合は、次のスニペットに示すように、DlTdoaRangingParams.Builder を使用してパラメータをビルドできます。これらのパラメータは DlTdoaRangingParams.createFromFiraConfigPacket() の代わりに使用できます。

Kotlin

val dlTdoaParams = DlTdoaRangingParams.Builder(1)
    .setComplexChannel(UwbComplexChannel.Builder()
            .setChannel(9).setPreambleIndex(10).build())
    .setDeviceAddress(deviceAddress)
    .setSessionKeyInfo(byteArrayOf(0x01, 0x02, 0x03, 0x04))
    .setRangingIntervalMillis(240)
    .setSlotDuration(UwbRangingParams.DURATION_2_MS)
    .setSlotsPerRangingRound(20)
    .setRangingRoundIndexes(byteArrayOf(0x01, 0x05))
    .build()

Java

DlTdoaRangingParams dlTdoaParams = new DlTdoaRangingParams.Builder(1)
    .setComplexChannel(new UwbComplexChannel.Builder()
            .setChannel(9).setPreambleIndex(10).build())
    .setDeviceAddress(deviceAddress)
    .setSessionKeyInfo(new byte[]{0x01, 0x02, 0x03, 0x04})
    .setRangingIntervalMillis(240)
    .setSlotDuration(UwbRangingParams.DURATION_2_MS)
    .setSlotsPerRangingRound(20)
    .setRangingRoundIndexes(new byte[]{0x01, 0x05})
    .build();