機能と API

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

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

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

コア機能

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

新しい ProfilingManager トリガー

Android 17 adds several new system triggers to ProfilingManager to help you collect in-depth data to debug performance issues.

The new triggers are:

To understand how to set up the system trigger, see the documentation on trigger-based profiling and how to retrieve and analyze profiling data documentation.

Profiling trigger for app anomalies

Android 17 introduces an on-device anomaly detection service that monitors for resource-intensive behaviors and potential compatibility regressions. Integrated with ProfilingManager, this service allows your app to receive profiling artifacts triggered by specific system-detected events.

Use the TRIGGER_TYPE_ANOMALY trigger to detect system performance issues such as excessive binder calls and excessive memory usage. When an app breaches OS-defined memory limits, the anomaly trigger allows developers to receive app-specific heap dumps to help identify and fix memory issues. Additionally, for excessive binder spam, the anomaly trigger provides a stack sampling profile on binder transactions.

This API callback occurs prior to any system imposed enforcements. For example, it can help developers collect debug data before the app is terminated by the system for exceeding memory limits.

val profilingManager =
    applicationContext.getSystemService(ProfilingManager::class.java)
val triggers = ArrayList<ProfilingTrigger>()
triggers.add(ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
val mainExecutor: Executor = Executors.newSingleThreadExecutor()
val resultCallback = Consumer<ProfilingResult> { profilingResult ->
    if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) {
        // upload profile result to server for further analysis
        setupProfileUploadWorker(profilingResult.resultFilePath)
    }
    profilingManager.registerForAllProfilingResults(mainExecutor,
                                                    resultCallback)
    profilingManager.addProfilingTriggers(triggers)
}

JobDebugInfo API

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

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

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

allow-while-idle アラームのリスナー サポートによりウェイクロックを削減

Android 17 では、PendingIntent の代わりに OnAlarmListener を受け取る AlarmManager.setExactAndAllowWhileIdle の新しいバリアントが導入されています。この新しいコールバック ベースのメカニズムは、ソケット接続を維持するメッセージング アプリなど、定期的なタスクを実行するために継続的なウェイクロックに依存しているアプリに最適です。

プライバシー

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

Encrypted Client Hello(ECH)プラットフォームのサポート

Android 17 では、ネットワーク通信のプライバシーを大幅に強化する Encrypted Client Hello(ECH)のプラットフォーム サポートが導入されています。ECH は、最初の TLS handshake 中に Server Name Indication(SNI)を暗号化する TLS 1.3 拡張機能です。この暗号化により、ネットワーク仲介者がアプリの接続先ドメインを特定することが難しくなり、ユーザーのプライバシーを保護できます。

プラットフォームに、ネットワーク ライブラリが ECH を実装するために必要な API が含まれるようになりました。これには、ECH 構成を含む HTTPS DNS レコードをクエリする DnsResolver の新機能と、ドメインに接続するときにこれらの構成を渡して ECH を有効にする Conscrypt の SSLEngines と SSLSockets の新しいメソッドが含まれます。デベロッパーは、ネットワーク セキュリティ構成ファイル内の新しい <domainEncryption> 要素を使用して、ECH の設定(機会的有効化や使用の義務付けなど)を構成できます。この設定は、グローバルに適用することも、ドメインごとに適用することもできます。

HttpEngine、WebView、OkHttp などの一般的なネットワーキング ライブラリは、今後のアップデートでこれらのプラットフォーム API を統合することが見込まれており、アプリが ECH を採用してユーザーのプライバシーを強化することが容易になります。

詳細については、暗号化されたクライアント Hello のドキュメントをご覧ください。

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 バージョンやデバイスとの完全な下位互換性を維持しながら、将来の量子攻撃からアプリを保護できます。

デベロッパーへの影響

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

接続

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

制約付き衛星ネットワーク

Implements optimizations to enable apps to function effectively over low-bandwidth satellite networks.

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

Android 17 には、ユーザー エクスペリエンスを向上させるための次の変更が含まれています。

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

Android 17 introduces a dedicated Assistant volume stream for Assistant apps, for playback with USAGE_ASSISTANT. This change decouples Assistant audio from the standard media stream, providing users with isolated control over both volumes. This enables scenarios such as muting media playback while maintaining audibility for Assistant responses, and the other way around.

Assistant apps with access to the new MODE_ASSISTANT_CONVERSATION audio mode can further improve the volume control consistency. Assistant apps can use this mode to provide a hint to the system about an active Assistant session, ensuring the Assistant stream can be controlled outside of the active USAGE_ASSISTANT playback or with connected Bluetooth peripherals.

引き継ぎ

Handoff is a new feature and API coming to Android 17 that app developers can integrate with to provide cross-device continuity for their users. It allows the user to start an app activity on one Android device and transition it to another Android device. Handoff runs in the background of a user's device and surfaces available activities from the user's other nearby devices through various entry points, like the launcher and taskbar, on the receiving device.

Apps can designate Handoff to launch the same native Android app, if it is installed and available on the receiving device. In this app-to-app flow, the user is deep-linked to the designated activity. Alternatively, app-to-web Handoff can be offered as a fallback option or directly implemented with URL Handoff.

Handoff support is implemented on a per-activity basis. To enable Handoff, call the setHandoffEnabled() method for the activity. Additional data may need to be passed along with the handoff so the recreated activity on the receiving device can restore appropriate state. Implement the onHandoffActivityDataRequested() callback to return a HandoffActivityData object which contains details that specify how Handoff should handle and recreate the activity on the receiving device.

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

With Android 17, Live Update launches the Semantic Coloring APIs to support colors with universal meaning.

The following classes support semantic coloring:

Coloring

  • Green: Associated with safety. This color should be used for the case where it lets people know you are in the safe situation.
  • Orange: For designating caution and marking physical hazards. This color should be used in the situation where users need to pay attention to set better protection setting.
  • Red: Generally indicates danger, stop. It should be presented for the case where need people's attention urgently.
  • Blue: Neutral color for content that is informational and should stand out from other content.

The following example shows how to apply semantic styles to text in a notification:

  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

Downlink Time Difference of Arrival (DL-TDoA) ranging lets a device determine its position relative to multiple anchors by measuring the relative arrival times of signals.

The following snippet demonstrates how to initialize the Ranging Manager, verify device capabilities, and start a DL-TDoA session:

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.RangingCapabilitiesCallback {
            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 = byteArrayOf(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.RangingCapabilitiesCallback() {
            @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());
        byte[] rangingRoundIndexes = new byte[] {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
        }
    }
}

Out-of-Band (OOB) Configurations

The following snippet provides an example of DL-TDoA OOB configuration data for Wi-Fi and BLE:

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
};

If you can't use an OOB configuration because it is missing, or if you need to change default values that aren't in the OOB config, you can build parameters with DlTdoaRangingParams.Builder as shown in the following snippet. You can use these parameters in place of 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();