Cihaz güvenlik durumunu yayınlama

Ö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:

AndroidX Security State Provider kitaplığı, ayrıcalıklı olmayan istemci uygulamaları ile cihaz üzerindeki güncelleme hizmetleri arasında standartlaştırılmış ve güvenli bir IPC çerçevesi oluşturur.

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:

  1. Müşterilerin sağlayıcınıza güvendiğini doğrulayın: Müşteri uygulamaları, önceden yüklenmiş olsa bile READ_PRIVILEGED_PHONE_STATE sertifikası 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=true ve permission=null karakterlerini kontrol edin:

    adb shell pm query-services --user 0 -a androidx.security.state.provider.UPDATE_INFO_SERVICE
    

    Bir müşteri sağlayıcınızı hâlâ görmüyorsa SecurityPatchState etiketinden Ignoring untrusted update provider için logcat'i kontrol edin.

  2. Hem 0 numaralı kullanıcı hem de iş profillerinde intent çözümlemesini doğrulayın: Android OS'in PackageManager, dışa aktarılan UPDATE_INFO_SERVICE intent 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_SERVICE
    
  3. dumpsys kullanarak 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) ve Cached Updates değerlerini bildirmek için dump() değerini geçersiz kılar. UpdateInfoService bağlı bir hizmet olduğundan ve istemciler sorgulama işleminden hemen sonra bağlantıyı kaldırdığından, istemci bağlı olmadığında dumpsys activity service çıkışı (nothing) olur. dumpsys komutunu ç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
    
  4. İ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 SecurityException olmadan bağlandığını ve onClientConnected(packageName, callerUid) tetiklediğini doğrulayın.
    • Push modeli sağlayıcılar (shouldFetchUpdates() == false) için: onRequestCompleted(telemetry) günlüklerinin UpdateFetchOutcome.CACHE_HIT (1) her sorguda fetchDurationMillis == 0 ile birlikte gönderildiğini doğrulayın.
    • Çekme modeli sağlayıcılar için (shouldFetchUpdates() == true): İlk eski önbellek sorgusunda onRequestCompleted(telemetry) günlüklerinin UpdateFetchOutcome.FETCHED (3) ve hemen sonraki sorgularda CACHE_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çin adb shell pm clear <your_package_name> komutunu çalıştırın.)

İ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:

UpdateInfoService, verilerin güncelliğini arka uç sunucu yüküne göre 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 genel lastCheckTimeMillis 1 saatten eski olduğunda true (eski bir önbelleği gösterir) değerini döndürür (TimeUnit.HOURS.toMillis(1)). shouldFetchUpdates(), false değerini döndürdüğünde hizmet, kilitleri almadan veya ağ G/Ç'si gerçekleştirmeden UpdateFetchOutcome.CACHE_HIT sonucuyla önbelleğe alınmış kayıtları hemen döndürür. Bu önbelleğe alma politikasını özelleştirmek için shouldFetchUpdates() değerini geçersiz kılabilirsiniz.
  • Yavaş Yol ve İstek Birleştirme: shouldFetchUpdates() döndüğünde true, hizmet dahili bir eşzamanlı rutin karşılıklı dışlama kilidi alır ve shouldFetchUpdates() değerini yeniden değerlendirir (kilit beklenirken eşzamanlı bir istek önbelleği yenilemişse UpdateFetchOutcome.COALESCED değ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çin shouldThrottle(), 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 sonra UpdateFetchOutcome.FAILED döndürülürse) sonraki 60 dakika boyunca yapılan sorgular, UpdateFetchOutcome.THROTTLED sonucuyla 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:

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

API referansı