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 ミリ秒間保留されたことを示します。
allow-while-idle アラームのリスナー サポートでウェイクロックを削減
Android 17 では、PendingIntent の代わりに OnAlarmListener を受け入れる AlarmManager.setExactAndAllowWhileIdle の新しいバリアントが導入されています。この新しいコールバック ベースのメカニズムは、現在継続的なウェイクロックに依存して定期的なタスクを実行しているアプリ(ソケット接続を維持するメッセージ アプリなど)に最適です。
プライバシー
Android 17 には、ユーザーのプライバシーを強化するための次の新機能が含まれています。
Android の連絡先選択ツール
Android の連絡先選択ツールは、ユーザーがアプリと連絡先を共有するための標準化された閲覧可能なインターフェースです。Android 17 以降を搭載したデバイスで利用でき、広範な 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 には、ユーザー エクスペリエンスを改善するための以下の変更が含まれています。
アシスタント専用の音量ストリーム
| Available to test? (Required build) | Yes (Android 17 Beta 1 or later) |
|---|---|
Requires changing targetSDKVersion? (API level) |
No (N/A) |
Requires changing compileSDKVersion? (API level) |
No (N/A) |
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.
ハンドオフ
ハンドオフは、Android 17 で導入される新しい機能と API です。アプリ デベロッパーはこれを統合して、ユーザーにデバイス間の継続性を提供できます。これにより、ユーザーは 1 つの Android デバイスでアプリ アクティビティを開始し、別の Android デバイスに移行できます。ハンドオフはユーザーのデバイスのバックグラウンドで実行され、受信側のデバイスのランチャーやタスクバーなどのさまざまなエントリ ポイントを通じて、ユーザーの近くにある他のデバイスで利用可能なアクティビティを表示します。
アプリは、受信側のデバイスに同じネイティブ Android アプリがインストールされていて利用可能な場合、ハンドオフでそのアプリを起動するように指定できます。このアプリ間フローでは、ユーザーは指定されたアクティビティにディープリンクされます。また、アプリからウェブへのハンドオフをフォールバック オプションとして提供したり、URL ハンドオフで直接実装したりすることもできます。
ハンドオフのサポートはアクティビティ単位で実装されます。ハンドオフを有効にするには、アクティビティの setHandoffEnabled() メソッドを呼び出します。受け渡しとともに追加のデータを渡す必要がある場合もあります。これにより、受信デバイスで再作成されたアクティビティが適切な状態を復元できます。onHandoffActivityRequested() コールバックを実装して、受け取り側のデバイスでハンドオフがアクティビティを処理して再作成する方法を指定する詳細を含む HandoffActivityData オブジェクトを返します。
ライブ アップデート - セマンティック カラー API
Android 17 では、ライブ アップデートを通じて、普遍的な意味を持つ色を サポートするセマンティック カラー API がリリースされます。
次のクラスはセマンティック カラーをサポートしています。
NotificationNotification.MetricNotification.ProgressStyle.PointNotification.ProgressStyle.Segment
色の意味
- 緑: 安全に関連付けられています。 安全な状況であることをユーザーに知らせる場合に使用します。
- オレンジ: 注意を促し、物理的な危険をマークするために使用します。この色は、ユーザーが保護設定を適切に行うために注意を払う必要がある状況で 使用してください。
- 赤: 一般的に危険、停止を示します。緊急にユーザーの注意を引く必要がある場合に表示します。
- 青: 情報提供を目的とし、他のコンテンツと区別する必要があるコンテンツに使用するニュートラルな色です。
次の例は、通知のテキストにセマンティック スタイルを適用する方法を示しています。
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();