Özgün donanım üreticisi (OEM) veya ayrıcalıklı bir kablosuz (OTA) güncelleme istemcisini yönetiyorsanız cihazdaki güvenlikle ilgili hassas uygulamaların, bekleyen güvenlik güncellemelerini görmesini sağlayabilirsiniz. Böylece bu uygulamalar, cihazın güvenlik durumunu doğru bir şekilde değerlendirebilir. Güçlü sıfır güven politikalarını uygulamak için uygulamaların yalnızca cihaza yüklenen güvenlik yaması seviyesini (Cihaz Güvenlik Yaması Düzeyi veya DSPL) değil, aynı zamanda hangi güvenlik güncellemelerinin kullanıma hazır olduğunu ve yüklenmeye hazır olduğunu (Kullanılabilir Güvenlik Yaması Düzeyi veya ASPL) doğrulayabilmesi gerekir.
Ayrıcalıklı olmayan istemci uygulamaları, donanım yazılımı özelliklerini doğrudan okuyamadığı, özel güncelleme hizmeti veritabanlarını inceleyemediği veya dahili OEM arka uç uç noktalarını sorgulayamadığı için AndroidX Security State Provider kitaplığı, güncelleme istemcilerinin kullanılabilir güncellemelerle ilgili bilgileri paylaşmak için kullandığı standartlaştırılmış ve güvenli bir işlemler arası iletişim (IPC) mimarisi sağlar. OTA güncelleme istemcinize bir UpdateInfoService uygulayarak tescilli arka uç entegrasyonlarını kullanıma sunmadan sistem için ASPL meta verilerini yayınlayabilirsiniz. Google, GMS cihazları için modüler sistem bileşeni (Mainline) güncelleyicilerinin uygulanmasını sağlarken GMS olmayan cihazlar da bu modüler sistem bileşenleri için ASPL meta verilerini yayınlayabilir.
Mimariye genel bakış
Aşağıdaki diyagramda, AndroidX Security State Provider kitaplığının, ayrıcalıklı olmayan istemci uygulamaları ile cihaz üzerindeki güncelleme hizmetleri arasında nasıl standartlaştırılmış ve güvenli bir IPC çerçevesi oluşturduğu gösterilmektedir:

Veri yayınlama modelleri
İstemci uygulamaları, queryAllAvailableUpdates veya fetchAvailableSecurityPatchLevel çağrısı yaparak güncelleme kullanılabilirliğini sorgular. Arka planda, istemci kitaplığı, READ_PRIVILEGED_PHONE_STATE iznine sahip sistem uygulamalarından cihazdaki UpdateInfoService sınıfını genişleten tüm kayıtlı hizmetleri otomatik olarak keşfeder ve bunlara bağlanır.
Önceki şemada gösterildiği gibi, security-state-provider kitaplığı iki veri yayınlama modelini destekler:
| Yayın modeli | Senkronizasyon tetikleyicisi | Müşteri yanıtı | Önerilen kullanım alanları |
|---|---|---|---|
| Push modeli (Arka plan senkronizasyonu) | Planlanmış arka plan çalışanları (WorkManager veya JobScheduler), arka uçunuzla senkronize olur ve UpdateInfoManager'a kayıt yazar. Hizmetiniz her zaman yerel disk önbelleğinden (shouldFetchUpdates() = false) yayın yapar. |
Hemen yerel önbellekten sunulur. | OEM sistem OTA güncelleyicileri ve arka planda senkronize edilen modüler bileşen güncelleyicileri. |
| Çekme modeli (İsteğe bağlı senkronizasyon) | Gelen istemci IPC sorguları, önbelleğe alınmış kayıtlar eski olduğunda bir ağ getirme işlemi tetikler (shouldFetchUpdates() = true). Mutex birleştirme ve sıklık sınırlama (shouldThrottle()), arka uçunuzu ani artışlardan korur. |
Önbellek eski olduğunda arka uç getirme işleminin tamamlanmasını bekler. | Planlanmış arka plan senkronizasyonu çalışanları olmayan tek parçalı OEM OTA güncelleyicileri. |
Birden fazla güncelleme sağlayıcı
Üretim Android cihazlarda birden fazla bağımsız güncelleme sağlayıcı aynı anda birlikte bulunur. Örneğin, Mainline, modüler bileşenlerin (COMPONENT_SYSTEM_MODULES) kullanılabilirliğini yayınlarken OEM OTA istemciniz, birincil işletim sistemi görüntüsünün (COMPONENT_SYSTEM) güncellemelerini yayınlar.
Hizmetinizin yalnızca yönettiği belirli bileşenlerle ilgili güncellemeleri kaydetmesi gerekir. Bir cihazdaki birden fazla sağlayıcı aynı bileşen için güncelleme yayınlarsa istemci uygulamaları, kurumsal denetim için mevcut en yüksek yama düzeyini (fetchAvailableSecurityPatchLevel() kullanılarak) değerlendirir veya ayrı UpdateInfo kayıtlarını (queryAllAvailableUpdates() kullanılarak) inceler. Hizmetinizin, bileşeniniz için her zaman kanonik biçimi (COMPONENT_SYSTEM için DateBasedSecurityPatchLevel) yayınladığından emin olun.
Güncelleme istemcinizi kullanmaya başlamayla ilgili adım adım açıklamalı kılavuz
AndroidX Security State Provider kitaplığını güncelleme istemcinize entegre etmek ve cihazınızın güvenlik güncellemesi kullanılabilirliğini yayınlamaya başlamak için aşağıdaki adımları uygulayın.
1. adım: Bağımlılıkları ekleyin
Güncelleme sağlayıcıyı uygulamak için projenizin Google Maven deposunu içerdiğinden emin olun, ardından security-state-provider kitaplığını modülünüzün build.gradle.kts (Kotlin DSL) veya build.gradle (Groovy DSL) dosyasına ekleyin:
Kotlin
// Kotlin DSL (build.gradle.kts)
dependencies {
// Core provider library for OTA and system update clients
implementation("androidx.security:security-state-provider:1.0.0")
// Required to construct UpdateInfo and DateBasedSecurityPatchLevel records
implementation("androidx.security:security-state:1.1.0")
// Optional: Guava ListenableFuture support for Java implementations
implementation("androidx.concurrent:concurrent-futures:1.2.0")
implementation("com.google.guava:guava:33.0.0-android")
}
Modern
// Groovy DSL (build.gradle)
dependencies {
// Core provider library for OTA and system update clients
implementation 'androidx.security:security-state-provider:1.0.0'
// Required to construct UpdateInfo and DateBasedSecurityPatchLevel records
implementation 'androidx.security:security-state:1.1.0'
// Optional: Guava ListenableFuture support for Java implementations
implementation 'androidx.concurrent:concurrent-futures:1.2.0'
implementation 'com.google.guava:guava:33.0.0-android'
}
2. adım: Manifest dosyanızda güncelleme hizmetini bildirin
Hizmetinizi, uygulamanızın AndroidManifest.xml bölümünde <intent-filter> ile eşleşen androidx.security.state.provider.UPDATE_INFO_SERVICE ile beyan edin. İstemci kitaplığının, özellikle iş profilleri için süreç ve kullanıcı sınırları arasında hizmete bağlanabilmesi amacıyla hizmetin dışa aktarılması (android:exported="true") ve tek kullanıcılı hizmet olarak yapılandırılması (android:singleUser="true") gerekir:
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.android.updater">
<application>
<service
android:name=".MyUpdateInfoService"
android:exported="true"
android:singleUser="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="androidx.security.state.provider.UPDATE_INFO_SERVICE" />
</intent-filter>
</service>
</application>
</manifest>
Güncelleyiciniz android.uid.system olarak çalışmıyorsa manifestinizde aşağıdaki izinleri de beyan edin ve ayrıcalıklı izin izin verilenler listenize eklediğinizden emin olun:
READ_PRIVILEGED_PHONE_STATE: Müşterilerin sağlayıcınıza güvenmesi için gereklidir.INTERACT_ACROSS_USERS:android:singleUser="true"için gereklidir.
3. adım: UpdateInfoService'i uygulayın
Güncelleme durumunuzu yayınlamak için UpdateInfoService sınıfını uygulamanız ve beklenen veri modeliyle eşleşen güncelleme kayıtları oluşturmanız gerekir.
UpdateInfo veri modeli spesifikasyonu
Push modelini veya Pull modelini seçmenize bakılmaksızın, aşağıdaki spesifikasyona göre UpdateInfo kayıtlarını UpdateInfo.Builder kullanarak oluşturun:
| Alan Adı | Getter Method (Getter Yöntemi) | Veri Türü | Doğrulama ve Biçim Koşulları | Amaç ve Sistem Anlamsal Yapısı |
|---|---|---|---|---|
component |
getComponent() |
String (@Component) |
SecurityPatchState hizmetindeki standart sabitler: COMPONENT_SYSTEM, COMPONENT_SYSTEM_MODULES veya COMPONENT_KERNEL. |
Bu güncellemenin hedeflediği yazılım veya donanım yazılımı alt sistemini tanımlar. |
securityPatchLevel |
getSecurityPatchLevel() |
SecurityPatchLevel |
DateBasedSecurityPatchLevel (YYYY-MM-DD) veya VersionedSecurityPatchLevel (major.minor.patch) örneği olmalı ya da SecurityPatchState.getComponentSecurityPatchLevel() kullanılarak ayrıştırılmalıdır. |
Bu güncelleme yüklendikten sonra ulaşılacak hedef güvenlik yaması seviyesi. |
publishedDateMillis |
getPublishedDateMillis() |
long |
Unix epoch'tan bu yana geçen milisaniye sayısı (System.currentTimeMillis()). > 0 olmalıdır. |
Güncellemenin kullanıcılara sunulduğu zaman (ör. OTA yayınlanma zamanı). Yük indirme veya yükleme süresini kullanmayın. |
lastCheckTimeMillis |
getLastCheckTimeMillis() |
long |
Unix epoch'tan bu yana geçen milisaniye. > 0 olmalıdır. |
Sağlayıcınızın senkronizasyon sırasında bu güncelleme kaydını doğruladığı veya keşfettiği zamanın zaman damgası. |
Aşağıdaki seçeneklerden güncelleyicinizin mimarisine uygun dağıtım modelini seçin:
1. seçenek: Push modeli (önerilir)
Arka plan senkronizasyonu çalışanı, OTA sunucunuzu kontrol ettiğinde bulunan güncellemelerin cihazın mevcut yama düzeyini yükselttiğini doğrulayın ve UpdateInfoManager.registerUpdate() kullanarak bu güncellemeleri kalıcı hale getirin. Güvenliği artıran bir güncelleme beklemiyorsa UpdateInfoManager.unregisterUpdate() işlevini çağırın. Her senkronizasyonun sonunda (bileşen başına UpdateInfo kaydını kalıcı hale getiren ancak istemcilere döndürülen genel son kontrol zaman damgasını güncellemeyen registerUpdate() çağrısından sonra bile) her zaman UpdateInfoManager.setLastCheckTimeMillis() çağrısı yapın. Bu işlemi Kotlin'de WorkManager CoroutineWorker veya Java'da Worker ile uygulayabilirsiniz:
Kotlin
import android.content.Context
import androidx.security.state.SecurityPatchState
import androidx.security.state.SecurityPatchState.DateBasedSecurityPatchLevel
import androidx.security.state.UpdateInfo
import androidx.security.state.provider.UpdateInfoManager
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import kotlin.math.max
class OtaSyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val updateInfoManager = UpdateInfoManager(applicationContext)
val securityPatchState = SecurityPatchState(applicationContext)
val currentSpl = securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
// 1. Fetch available update metadata from OEM backend
val latestUpdate = MyOtaClient.fetchLatestSystemUpdate()
val targetSplString = latestUpdate?.spl?.trim()
val targetSpl = if (!targetSplString.isNullOrEmpty()) {
DateBasedSecurityPatchLevel.fromString(targetSplString)
} else {
null
}
// 2. Defensively verify that target SPL is non-blank AND strictly newer than installed DSPL.
// If an update is a maintenance patch with no SPL increment (or if no update is available),
// unregister any stale cached record for this component.
if (latestUpdate != null && targetSpl != null && targetSpl > currentSpl) {
val updateInfo = UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.setSecurityPatchLevel(targetSpl)
.setPublishedDateMillis(latestUpdate.releaseTimeMillis)
.setLastCheckTimeMillis(System.currentTimeMillis())
.build()
updateInfoManager.registerUpdate(updateInfo)
} else {
val clearTarget = UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build()
updateInfoManager.unregisterUpdate(clearTarget)
}
// 3. Update global freshness timestamp (monotonic synchronization)
val currentCheckTime = System.currentTimeMillis()
val previousCheckTime = updateInfoManager.getLastCheckTimeMillis()
updateInfoManager.setLastCheckTimeMillis(max(previousCheckTime, currentCheckTime))
return Result.success()
}
}
Java
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.security.state.SecurityPatchState;
import androidx.security.state.SecurityPatchState.DateBasedSecurityPatchLevel;
import androidx.security.state.SecurityPatchState.SecurityPatchLevel;
import androidx.security.state.UpdateInfo;
import androidx.security.state.provider.UpdateInfoManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class OtaSyncWorker extends Worker {
public OtaSyncWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
// In Java, pass null for customSecurityState because UpdateInfoManager does not declare @JvmOverloads
UpdateInfoManager updateInfoManager =
new UpdateInfoManager(getApplicationContext(), /* customSecurityState= */ null);
SecurityPatchState securityPatchState = new SecurityPatchState(getApplicationContext());
SecurityPatchLevel currentSpl =
securityPatchState.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM);
// 1. Fetch available update metadata from OEM backend
MyOtaUpdate latestUpdate = MyOtaClient.fetchLatestSystemUpdate();
String targetSplString = (latestUpdate != null && latestUpdate.getSpl() != null)
? latestUpdate.getSpl().trim()
: null;
DateBasedSecurityPatchLevel targetSpl =
!TextUtils.isEmpty(targetSplString)
? DateBasedSecurityPatchLevel.fromString(targetSplString)
: null;
// 2. Defensively verify that target SPL is non-blank AND strictly newer than installed DSPL.
// If an update is a maintenance patch with no SPL increment (or if no update is available),
// unregister any stale cached record for this component.
if (latestUpdate != null && targetSpl != null && targetSpl.compareTo(currentSpl) > 0) {
UpdateInfo updateInfo = new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.setSecurityPatchLevel(targetSpl)
.setPublishedDateMillis(latestUpdate.getReleaseTimeMillis())
.setLastCheckTimeMillis(System.currentTimeMillis())
.build();
updateInfoManager.registerUpdate(updateInfo);
} else {
UpdateInfo clearTarget = new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build();
updateInfoManager.unregisterUpdate(clearTarget);
}
// 3. Update global freshness timestamp (monotonic synchronization)
long currentCheckTime = System.currentTimeMillis();
long previousCheckTime = updateInfoManager.getLastCheckTimeMillis();
updateInfoManager.setLastCheckTimeMillis(Math.max(previousCheckTime, currentCheckTime));
return Result.success();
}
}
Anında iletmeye dayalı bir modelde, arka plan görevleri güncelleme kayıtlarını doğrudan UpdateInfoManager'ya kalıcı olarak yazar. Çerçevenin her zaman yerel disk depolama alanındaki kayıtları sunmasını sağlamak için Kotlin'de UpdateInfoService veya Java'da ListenableFutureUpdateInfoService öğesini genişleterek shouldFetchUpdates() öğesini geçersiz kılın ve false değerini döndürün:
Kotlin
import androidx.security.state.UpdateInfo
import androidx.security.state.provider.UpdateInfoService
class PushUpdateInfoService : UpdateInfoService() {
// Cache is populated out-of-band by background sync tasks
override fun shouldFetchUpdates(): Boolean = false
// Never invoked under normal flow because shouldFetchUpdates() returns false
override suspend fun fetchUpdates(): List<UpdateInfo> = emptyList()
}
Java
import androidx.annotation.NonNull;
import androidx.security.state.UpdateInfo;
import androidx.security.state.provider.ListenableFutureUpdateInfoService;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
public class PushUpdateInfoService extends ListenableFutureUpdateInfoService {
@Override
protected boolean shouldFetchUpdates() {
return false;
}
@NonNull
@Override
protected ListenableFuture<List<UpdateInfo>> fetchUpdatesAsync() {
return Futures.immediateFuture(Collections.emptyList());
}
}
B seçeneği: Çekme modeli (isteğe bağlı)
Çekmeye dayalı mimaride hizmetiniz, yerel önbellek eski olduğunda istemci uygulamaları tarafından tetiklenen isteğe bağlı yenileme isteklerini işler.
İsteğe bağlı güncelleme sorgularını işlemek için Kotlin'de UpdateInfoService'i (askıya alma fetchUpdates() işlevini uygulayarak) veya Java'da ListenableFutureUpdateInfoService'ı (Guava ListenableFuture döndüren fetchUpdatesAsync()'ı uygulayarak) genişletin:
Kotlin
package com.example.android.updater
import androidx.security.state.SecurityPatchState
import androidx.security.state.SecurityPatchState.DateBasedSecurityPatchLevel
import androidx.security.state.UpdateInfo
import androidx.security.state.provider.UpdateInfoManager
import androidx.security.state.provider.UpdateInfoService
import java.util.concurrent.TimeUnit
class MyUpdateInfoService : UpdateInfoService() {
// Manage local update records and check timestamps
private val updateInfoManager by lazy { UpdateInfoManager(this) }
override suspend fun fetchUpdates(): List<UpdateInfo> {
val currentSpl = SecurityPatchState(this)
.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM)
// 1. Execute network request to OTA backend
val response = MyOtaBackendClient.checkAvailableUpdates()
// 2. Defensively filter out blank or non-advancing SPLs and map to UpdateInfo objects
val validUpdates = response.updates
.mapNotNull { updateItem ->
val splString = updateItem.targetSpl?.trim()
if (splString.isNullOrEmpty()) return@mapNotNull null
val parsedSpl = DateBasedSecurityPatchLevel.fromString(splString)
if (parsedSpl > currentSpl) {
UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.setSecurityPatchLevel(parsedSpl)
.setPublishedDateMillis(updateItem.releaseTimestampMillis)
.setLastCheckTimeMillis(System.currentTimeMillis())
.build()
} else {
null
}
}
// 3. If no advancing SYSTEM update is available (or if a previously offered update was revoked),
// proactively unregister any cached record for this component.
if (validUpdates.isEmpty()) {
val clearTarget = UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build()
updateInfoManager.unregisterUpdate(clearTarget)
}
return validUpdates
}
override fun shouldFetchUpdates(): Boolean {
// Enforce custom freshness threshold (for example, 4 hours instead of default 1 hour)
val lastCheckMillis = updateInfoManager.getLastCheckTimeMillis()
val dataAge = System.currentTimeMillis() - lastCheckMillis
return dataAge > TimeUnit.HOURS.toMillis(4)
}
}
Java
package com.example.android.updater;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.security.state.SecurityPatchState;
import androidx.security.state.SecurityPatchState.DateBasedSecurityPatchLevel;
import androidx.security.state.SecurityPatchState.SecurityPatchLevel;
import androidx.security.state.UpdateInfo;
import androidx.security.state.provider.ListenableFutureUpdateInfoService;
import androidx.security.state.provider.UpdateInfoManager;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class MyUpdateInfoService extends ListenableFutureUpdateInfoService {
private UpdateInfoManager updateInfoManager;
@Override
public void onCreate() {
super.onCreate();
// Pass null for customSecurityState because UpdateInfoManager does not declare @JvmOverloads
updateInfoManager = new UpdateInfoManager(this, /* customSecurityState= */ null);
}
@NonNull
@Override
protected ListenableFuture<List<UpdateInfo>> fetchUpdatesAsync() {
try {
SecurityPatchLevel currentSpl = new SecurityPatchState(this)
.getDeviceSecurityPatchLevel(SecurityPatchState.COMPONENT_SYSTEM);
MyOtaBackendResponse response = MyOtaBackendClient.checkAvailableUpdates();
List<UpdateInfo> updates = new ArrayList<>();
for (MyOtaUpdateItem item : response.getUpdates()) {
String trimmedSpl = (item.getTargetSpl() != null) ? item.getTargetSpl().trim() : null;
if (!TextUtils.isEmpty(trimmedSpl)) {
DateBasedSecurityPatchLevel parsedSpl =
DateBasedSecurityPatchLevel.fromString(trimmedSpl);
if (parsedSpl.compareTo(currentSpl) > 0) {
updates.add(new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.setSecurityPatchLevel(parsedSpl)
.setPublishedDateMillis(item.getReleaseTimestampMillis())
.setLastCheckTimeMillis(System.currentTimeMillis())
.build());
}
}
}
// If no advancing SYSTEM update is available (or if a previously offered update was revoked),
// proactively unregister any cached record for this component.
if (updates.isEmpty()) {
UpdateInfo clearTarget = new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build();
updateInfoManager.unregisterUpdate(clearTarget);
}
return Futures.immediateFuture(updates);
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
}
@Override
protected boolean shouldFetchUpdates() {
long lastCheckMillis = updateInfoManager.getLastCheckTimeMillis();
long dataAge = System.currentTimeMillis() - lastCheckMillis;
return dataAge > TimeUnit.HOURS.toMillis(4);
}
}
4. adım: Cihaz yeniden başlatıldıktan sonra uygulanan güncellemeleri temizleyin
UpdateInfoManager, registerUpdate() çağrıldığında eski güncellemeleri otomatik olarak temizlerken güncelleyiciniz, bir OTA güncellemesinin yüklenmesi tamamlandıktan sonraki planlanmış sunucu senkronizasyon döngüsüne kadar registerUpdate()'i tekrar çağırmaz. Yeniden başlatmadan hemen sonra istemci uygulamalarının, yüklü bir güncellemeyi hala beklemede olarak görmesini önlemek için ACTION_BOOT_COMPLETED dinleyin ve OTA güncellemesinin yüklenmesi tamamlandığında UpdateInfoManager.unregisterUpdate()'ı çağırarak kaydı yerel önbellekten temizleyin. Bu işlemi yalnızca bir güncellemenin yüklenmesi tamamlandığında yapmak, her normal cihaz yeniden başlatıldığında bekleyen (kaldırılmış) güncellemelerin koşulsuz olarak silinmesini önler. UpdateInfoManager anahtarları kayıtları bileşene göre güncellediğinden kaydı silme için UpdateInfo nesnesini oluştururken yalnızca hedef bileşeni belirtmeniz gerekir:
Kotlin
// Build target identifying the component to unregister
val target = UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build()
// Unregister the update to remove it from disk cache
updateInfoManager.unregisterUpdate(target)
// Refresh last check timestamp to indicate up-to-date state
updateInfoManager.setLastCheckTimeMillis(System.currentTimeMillis())
Java
// Build target identifying the component to unregister
UpdateInfo target = new UpdateInfo.Builder()
.setComponent(SecurityPatchState.COMPONENT_SYSTEM)
.build();
// Unregister the update to remove it from disk cache
updateInfoManager.unregisterUpdate(target);
// Refresh last check timestamp to indicate up-to-date state
updateInfoManager.setLastCheckTimeMillis(System.currentTimeMillis());
5. adım: Entegrasyonunuzu doğrulayın
Uçtan uca entegrasyonu doğrulamak ve yaygın OEM dağıtım tuzaklarını önlemek için Android Debug Bridge (ADB)'i kullanarak bir Android cihazda veya emülatörde aşağıdaki kontrolleri çalıştırın:
Müşterilerin sağlayıcınıza güvendiğini doğrulayın: Müşteri uygulamaları, önceden yüklenmiş olsa bile
READ_PRIVILEGED_PHONE_STATEsertifikası olmayan sağlayıcıları yok sayar. İznin verildiğini onaylayın:adb shell dumpsys package <your_package_name> | grep "READ_PRIVILEGED_PHONE_STATE: granted=true"Ardından, hizmetinizin bulunabilir olduğunu ve hizmet izni olmadığını onaylayın. Hizmetinizin çıktısında
exported=truevepermission=nullkarakterlerini kontrol edin:adb shell pm query-services --user 0 -a androidx.security.state.provider.UPDATE_INFO_SERVICEBir müşteri sağlayıcınızı hâlâ görmüyorsa
SecurityPatchStateetiketindenIgnoring untrusted update provideriçin logcat'i kontrol edin.Hem 0 numaralı kullanıcı hem de iş profillerinde intent çözümlemesini doğrulayın: Android OS'in
PackageManager, dışa aktarılanUPDATE_INFO_SERVICEintent filtrenizi hem birincil kullanıcıda (User 0) hem de etkin Android Enterprise iş profillerinde (ör.User 10) çözdüğünü onaylayın:adb shell pm query-services --user 0 -a androidx.security.state.provider.UPDATE_INFO_SERVICE adb shell pm query-services --user 10 -a androidx.security.state.provider.UPDATE_INFO_SERVICEdumpsyskullanarak hizmet durumunu ve önbelleğe alınmış kayıtları doğrulayın:UpdateInfoService,Global Last Check,Should Throttle(hız sınırlayıcının durumu) veCached Updatesdeğerlerini bildirmek içindump()değerini geçersiz kılar.UpdateInfoServicebağlı bir hizmet olduğundan ve istemciler sorgulama işleminden hemen sonra bağlantıyı kaldırdığından, istemci bağlı olmadığındadumpsys activity serviceçıkışı(nothing)olur.dumpsyskomutunu çalıştırmadan önce hizmeti açıkça başlatın:adb shell am start-service -a androidx.security.state.provider.UPDATE_INFO_SERVICE <your_package_name>/.<service_class_name> adb shell dumpsys activity service <your_package_name>/.<service_class_name>Örnek teşhis çıkışı:
UpdateInfoService State: Active Requests: 0 Global Last Check: Thu Jan 01 12:00:00 UTC 2026 Should Throttle: false Cached Updates (1): - Component: SYSTEM SPL: 2026-01-01 Published: Thu Jan 01 00:00:00 UTC 2026 Last Checked: Thu Jan 01 12:00:00 UTC 2026İstemci bağlamayı tetikleyin ve telemetri sonuçlarını doğrulayın: Ayrıcalıklı olmayan bir test uygulamasından (sistem imzası izinlerine sahip olmayan)
SecurityPatchState.queryAllAvailableUpdates()öğesini çağırın. Telemetri geri aramalarını uyguladıysanız aşağıdakileri kontrol edin:- Ayrıcalıksız istemcinin
SecurityExceptionolmadan bağlandığını veonClientConnected(packageName, callerUid)tetiklediğini doğrulayın. - Push modeli sağlayıcılar (
shouldFetchUpdates() == false) için:onRequestCompleted(telemetry)günlüklerininUpdateFetchOutcome.CACHE_HIT(1) her sorgudafetchDurationMillis == 0ile birlikte gönderildiğini doğrulayın. - Çekme modeli sağlayıcılar için (
shouldFetchUpdates() == true): İlk eski önbellek sorgusundaonRequestCompleted(telemetry)günlüklerininUpdateFetchOutcome.FETCHED(3) ve hemen sonraki sorgulardaCACHE_HIT(1) döndürdüğünü doğrulayın. (Çekme modeli test çalıştırmaları arasında 1 saatlik kalıcı hız sınırlayıcıyı sıfırlamak içinadb shell pm clear <your_package_name>komutunu çalıştırın.)
- Ayrıcalıksız istemcinin
İsteğe bağlı ve gelişmiş yapılandırmalar
Önbelleğe alma politikası ve sıklık sınırlaması
Bir istemci güncellemeleri sorguladığında UpdateInfoService, veri güncelliğini arka uç sunucu yüküyle dengelemek için iki kez kontrol edilen bir kilitleme iş akışı yürütür:

- Hızlı Yol (
shouldFetchUpdates()): Varsayılan olarak,shouldFetchUpdates()yalnızca genellastCheckTimeMillis1 saatten eski olduğundatrue(eski bir önbelleği gösterir) değerini döndürür (TimeUnit.HOURS.toMillis(1)).shouldFetchUpdates(),falsedeğerini döndürdüğünde hizmet, kilitleri almadan veya ağ G/Ç'si gerçekleştirmedenUpdateFetchOutcome.CACHE_HITsonucuyla önbelleğe alınmış kayıtları hemen döndürür. Bu önbelleğe alma politikasını özelleştirmek içinshouldFetchUpdates()değerini geçersiz kılabilirsiniz. - Yavaş Yol ve İstek Birleştirme:
shouldFetchUpdates()döndüğündetrue, hizmet dahili bir eşzamanlı rutin karşılıklı dışlama kilidi alır veshouldFetchUpdates()değerini yeniden değerlendirir (kilit beklenirken eşzamanlı bir istek önbelleği yenilemişseUpdateFetchOutcome.COALESCEDdeğerini döndürür). - Kalıcı sıklık sınırlayıcı (
shouldThrottle()): Arka uç altyapısını sorgu patlamalarından veya tekrarlanan hatalardan korumak içinshouldThrottle(), uygulama ve cihaz yeniden başlatmaları arasında kalıcı olarak en az 1 saatlik aralık uygular.UpdateInfoService,fetchUpdates()çağrılmadan önce her denemeyi kaydeder. Bu nedenle,fetchUpdates()bir istisna oluşturursa (onFetchFailed(e)çağrıldıktan sonraUpdateFetchOutcome.FAILEDdöndürülürse) sonraki 60 dakika boyunca yapılan sorgular,UpdateFetchOutcome.THROTTLEDsonucuyla birlikte önbelleğe alınmış yedek verileri sorunsuz bir şekilde döndürür.
Gözlemlenebilirlik, telemetri ve teşhis
UpdateInfoService, düşük düzeyli AIDL saplarını kullanmadan istemci kullanımını izlemek, IPC gecikmesini izlemek ve arka uç hatalarını kaydetmek için yerleşik gözlemlenebilirlik kancaları sağlar:
onRequestCompleted(telemetry): Her güncelleme kontrolü tamamlandığındaUpdateCheckTelemetryözetiyle birlikte çağrılır.onClientConnected(packageName, callerUid): Doğrulanmış bir istemci oturum açtığında çağrılır.onClientDisconnected(packageName, callerUid): Bir istemcinin bağlantısı kaldırıldığında veya işlemi sonlandırıldığında çağrılır.onFetchFailed(e): Hizmet, önbelleğe alınmış yedek verileri döndürmeden öncefetchUpdates()sırasında bir istisna oluşursa çağrılır.
Bu geri çağırmaları UpdateInfoService (Kotlin) veya ListenableFutureUpdateInfoService (Java) içinde geçersiz kılın:
Kotlin
import androidx.security.state.provider.UpdateCheckTelemetry
import androidx.security.state.provider.UpdateFetchOutcome
import androidx.security.state.provider.UpdateInfoService
abstract class MonitoredUpdateInfoService : UpdateInfoService() {
override fun onRequestCompleted(telemetry: UpdateCheckTelemetry) {
val outcomeName = when (telemetry.outcome) {
UpdateFetchOutcome.CACHE_HIT -> "CACHE_HIT"
UpdateFetchOutcome.COALESCED -> "COALESCED"
UpdateFetchOutcome.FETCHED -> "FETCHED"
UpdateFetchOutcome.THROTTLED -> "THROTTLED"
UpdateFetchOutcome.FAILED -> "FAILED"
else -> "UNKNOWN"
}
MyAnalytics.logEvent("SECURITY_UPDATE_CHECK")
.addParam("outcome", outcomeName)
.addParam("total_duration_ms", telemetry.totalDurationMillis)
.addParam("lock_wait_ms", telemetry.lockWaitDurationMillis)
.addParam("processing_ms", telemetry.processingDurationMillis)
.addParam("fetch_duration_ms", telemetry.fetchDurationMillis)
.addParam("caller_uid", telemetry.callerUid)
.send()
}
override fun onClientConnected(packageName: String, callerUid: Int) {
// Track authenticated client sessions and adoption
MyMetrics.incrementCounter("client_connected", "package", packageName)
}
override fun onClientDisconnected(packageName: String, callerUid: Int) {
// Track session termination and cleanup resources
MyMetrics.incrementCounter("client_disconnected", "package", packageName)
}
override fun onFetchFailed(e: Exception) {
// Report exceptions caught during the update check workflow
MyCrashReporter.recordException(e)
}
}
Java
import androidx.annotation.NonNull;
import androidx.security.state.provider.ListenableFutureUpdateInfoService;
import androidx.security.state.provider.UpdateCheckTelemetry;
import androidx.security.state.provider.UpdateFetchOutcome;
public abstract class MonitoredUpdateInfoService extends ListenableFutureUpdateInfoService {
@Override
protected void onRequestCompleted(@NonNull UpdateCheckTelemetry telemetry) {
String outcomeName;
switch (telemetry.getOutcome()) {
case UpdateFetchOutcome.CACHE_HIT: outcomeName = "CACHE_HIT"; break;
case UpdateFetchOutcome.COALESCED: outcomeName = "COALESCED"; break;
case UpdateFetchOutcome.FETCHED: outcomeName = "FETCHED"; break;
case UpdateFetchOutcome.THROTTLED: outcomeName = "THROTTLED"; break;
case UpdateFetchOutcome.FAILED: outcomeName = "FAILED"; break;
default: outcomeName = "UNKNOWN"; break;
}
MyAnalytics.logEvent("SECURITY_UPDATE_CHECK")
.addParam("outcome", outcomeName)
.addParam("total_duration_ms", telemetry.getTotalDurationMillis())
.addParam("lock_wait_ms", telemetry.getLockWaitDurationMillis())
.addParam("processing_ms", telemetry.getProcessingDurationMillis())
.addParam("fetch_duration_ms", telemetry.getFetchDurationMillis())
.addParam("caller_uid", telemetry.getCallerUid())
.send();
}
@Override
protected void onClientConnected(@NonNull String packageName, int callerUid) {
MyMetrics.incrementCounter("client_connected", "package", packageName);
}
@Override
protected void onClientDisconnected(@NonNull String packageName, int callerUid) {
MyMetrics.incrementCounter("client_disconnected", "package", packageName);
}
@Override
protected void onFetchFailed(@NonNull Exception e) {
MyCrashReporter.recordException(e);
}
}
Telemetri sonuçları ve gecikme metrikleri
UpdateCheckTelemetry, tekdüze geçen süreleri ölçer
(SystemClock.elapsedRealtime()) ve UpdateFetchOutcome'da tanımlanan beş sonuçtan birini bildirir:
| Sonuç Sabiti | @IntDef Kod |
Kaydedilen Metrik Özellikleri | Açıklama ve Sistem Durumu |
|---|---|---|---|
UpdateFetchOutcome.CACHE_HIT |
1 |
totalDurationMillis, processingDurationMillis, callerUid |
Hızlı Yol'da yerel disk/RAM önbelleğinden hemen sunulur (shouldFetchUpdates() döndürülür false). lockWaitDurationMillis ve fetchDurationMillis, 0'dir. |
UpdateFetchOutcome.COALESCED |
2 |
totalDurationMillis, lockWaitDurationMillis, processingDurationMillis, callerUid |
Sorgu, başka bir etkin yenilemenin arkasında sıraya alındı. Kilit alındıktan sonra veriler günceldi. Yinelenen ağ getirme işlemi önlendi (fetchDurationMillis, 0). |
UpdateFetchOutcome.FETCHED |
3 |
totalDurationMillis, lockWaitDurationMillis, processingDurationMillis, fetchDurationMillis, callerUid |
Arka uç ağ senkronizasyonu başarıyla yürütüldü (fetchUpdates() tamamlandı). Yeni kayıtlar diske kalıcı olarak kaydedildi. |
UpdateFetchOutcome.THROTTLED |
4 |
totalDurationMillis, lockWaitDurationMillis, processingDurationMillis, callerUid |
İstek, sıklık sınırlayıcı tarafından engellendi (shouldThrottle() döndürüldü true). Önbelleğe alınan veriler istemciye güvenli bir şekilde döndürüldü (fetchDurationMillis 0). |
UpdateFetchOutcome.FAILED |
5 |
totalDurationMillis, lockWaitDurationMillis, processingDurationMillis, fetchDurationMillis, callerUid |
Güncelleme kontrolü veya ağ isteği istisna oluşturdu. İstisna güvenlik duvarı tarafından yakalandı, onFetchFailed(e) tetiklendi, önbelleğe alınmış yedek döndürüldü. |
Gelişmiş hizmet aracısı kancası: getCallerUid()
Bir istemci bağlandığında UpdateInfoService, ilk Binder iş parçacığında getCallerUid() öğesini otomatik olarak değerlendirir (fetchUpdates() öncesinde Binder.clearCallingIdentity() çağrılmadan önce), paket sahipliğini doğrular ve doğrulanmış arayan UID'sini doğrudan onRequestCompleted(telemetry) içindeki onClientConnected(), onClientDisconnected() ve telemetry.callerUid öğelerine iletir.
Standart Android <service> bileşenleri için çağırmanız veya geçersiz kılmanız gerekmez
getCallerUid(). protected open getCallerUid() yöntemi (varsayılan olarak Binder.getCallingUid()'ye temsilci olarak atanır), Binder IPC'yi dahili bir hizmet aracısı veya proxy mimarisi üzerinden yönlendiren ana makine uygulamaları için geçersiz kılma kancası olarak sağlanır. Bu sayede, alt sınıfın aracının UID'si yerine mantıksal istemci UID'sini döndürmesine olanak tanır.
Ek kaynaklar
Güvenlik durumunu yayınlama hakkında daha fazla bilgi için aşağıdaki kaynaklara bakın:
Belgeler
- Cihaz güvenlik durumunu anlama
- Android Güvenlik Bültenleri
- Modüler sistem bileşenleri
- Ek güvenlik yamaları
- Security State Provider 1.0.0 sürüm notları
API referansı
UpdateInfoServiceListenableFutureUpdateInfoServiceUpdateInfoManagerUpdateInfoUpdateInfo.BuilderUpdateCheckTelemetryUpdateFetchOutcomeSecurityPatchState