আপনি যদি একজন অরিজিনাল ইকুইপমেন্ট ম্যানুফ্যাকচারার (OEM) হন বা একটি বিশেষ ওভার-দ্য-এয়ার (OTA) আপডেট ক্লায়েন্ট বজায় রাখেন, তাহলে আপনি ডিভাইসে নিরাপত্তা-সংবেদনশীল অ্যাপগুলিকে মুলতুবি থাকা নিরাপত্তা আপডেট সম্পর্কে দৃশ্যমানতা দিতে পারেন যাতে তারা সঠিকভাবে ডিভাইসের নিরাপত্তা সংক্রান্ত অবস্থান মূল্যায়ন করতে পারে। কঠোরভাবে জিরো-ট্রাস্ট নীতি প্রয়োগ করতে, অ্যাপগুলিকে অবশ্যই ডিভাইসে ইনস্টল করা প্যাচ লেভেল (ডিভাইস নিরাপত্তা প্যাচ লেভেল বা DSPL) ছাড়াও, কোন কোন নিরাপত্তা আপডেট উপলভ্য এবং ইনস্টল করার জন্য রেডি (উপলভ্য নিরাপত্তা প্যাচ লেভেল বা ASPL) তা যাচাই করতে হবে।
কারণ, বিশেষ সুবিধা নেই এমন ক্লায়েন্ট অ্যাপ সরাসরি ফার্মওয়্যার
প্রপার্টি পড়তে, ব্যক্তিগত আপডেটার ডেটাবেস পরিদর্শন করতে বা ইন্টার্নাল OEM ব্যাকএন্ড
এন্ডপয়েন্ট কোয়েরি করতে পারে না। তাই, AndroidX Security State Provider লাইব্রেরি একটি
স্ট্যান্ডার্ডাইজড, সুরক্ষিত ইন্টারপ্রসেস কমিউনিকেশন (IPC) আর্কিটেকচার প্রদান করে যা
উপলভ্য আপডেট সম্পর্কে তথ্য শেয়ার করতে আপডেট ক্লায়েন্ট ব্যবহার করে। আপনার OTA আপডেট ক্লায়েন্টে
UpdateInfoService প্রয়োগ করার মাধ্যমে, আপনি মালিকানাধীন ব্যাকএন্ড ইন্টিগ্রেশন প্রকাশ না করেই সিস্টেমের জন্য ASPL
মেটাডেটা প্রকাশ করতে পারবেন। GMS ডিভাইসের জন্য
মডুলার সিস্টেম কম্পোনেন্ট (Mainline)
আপডেটার প্রয়োগ করার সুবিধা Google দিলেও, GMS নয় এমন ডিভাইসও
এইসব মডুলার সিস্টেম কম্পোনেন্টের জন্য ASPL মেটাডেটা প্রকাশ করতে পারে।
আর্কিটেকচার ওভারভিউ
নিচের ডায়াগ্রাম থেকে বোঝা যায় যে কীভাবে AndroidX Security State Provider লাইব্রেরি, বিশেষ সুবিধা নেই এমন ক্লায়েন্ট অ্যাপ এবং ডিভাইসে থাকা আপডেট পরিষেবার মধ্যে একটি স্ট্যান্ডার্ড, সুরক্ষিত IPC ফ্রেমওয়ার্ক তৈরি করে:

ডেটা ডেলিভারি মডেল
ক্লায়েন্ট অ্যাপ,
queryAllAvailableUpdates বা
fetchAvailableSecurityPatchLevel-এ কল করে আপডেট উপলভ্য আছে কিনা তা কোয়েরি করে। ডিভাইসে, ক্লায়েন্ট
লাইব্রেরি অটোমেটিক সব রেজিস্টার করা পরিষেবা খুঁজে পায় এবং সেগুলির সাথে বাইন্ড করে। এই পরিষেবাগুলি সিস্টেম অ্যাপ থেকে UpdateInfoService ক্লাস এক্সটেন্ড করে এবং সেগুলির কাছে READ_PRIVILEGED_PHONE_STATE অনুমতি
থাকে।
আগের ডায়াগ্রামে দেখানো হয়েছে যে, security-state-provider লাইব্রেরি
দুটি ডেটা ডেলিভারি মডেল কাজ করে:
| ডেলিভারি মডেল | সিঙ্ক্রোনাইজেশন ট্রিগার | ক্লায়েন্টের উত্তর | সাজেস্ট করা ব্যবহারিক ক্ষেত্র |
|---|---|---|---|
| পুশ মডেল (ব্যাকগ্রাউন্ড সিঙ্ক) | শিডিউল করা ব্যাকগ্রাউন্ড ওয়ার্কার (WorkManager বা JobScheduler) আপনার ব্যাকএন্ডের সাথে সিঙ্ক করে এবং UpdateInfoManager-এ রেকর্ড লেখে। আপনার পরিষেবা সবসময় লোকাল ডিস্ক ক্যাশে (shouldFetchUpdates() = false) থেকে পরিবেশন করে। |
স্থানীয় ক্যাশে থেকে সঙ্গে সঙ্গে পরিবেশন করা হয়। | OEM সিস্টেম OTA আপডেটার এবং ব্যাকগ্রাউন্ডে সিঙ্ক করা মডুলার কম্পোনেন্ট আপডেটার। |
| পুল মডেল (চাহিদা অনুযায়ী সিঙ্ক করা) | ইনকামিং ক্লায়েন্ট IPC কোয়েরি, ক্যাশে করা রেকর্ড পুরনো হয়ে গেলে (shouldFetchUpdates() = true) নেটওয়ার্ক ফেচ ট্রিগার করে। মিউটেক্স কোয়ালিসিং ও রেট লিমিটিং (shouldThrottle()) আপনার ব্যাকএন্ডকে স্পাইক থেকে সুরক্ষিত রাখে। |
ক্যাশে পুরনো হয়ে গেলে ব্যাকএন্ড থেকে ডেটা ফেচ করার জন্য অপেক্ষা করে। | শিডিউল করা ব্যাকগ্রাউন্ড সিঙ্ক ওয়ার্কার ছাড়া মনোলিথিক OEM OTA আপডেটার। |
একাধিক আপডেট প্রদানকারী
প্রোডাকশন Android ডিভাইসে, একাধিক স্বতন্ত্র আপডেট প্রদানকারী একসাথে
কাজ করে। যেমন, Mainline মডিউলার
কম্পোনেন্ট (COMPONENT_SYSTEM_MODULES)-এর উপলভ্যতা প্রকাশ করে, অন্যদিকে আপনার OEM OTA ক্লায়েন্ট
প্রাইমারি OS ইমেজ (COMPONENT_SYSTEM)-এর জন্য আপডেট প্রকাশ করে।
আপনার পরিষেবা শুধুমাত্র সেইসব নির্দিষ্ট কম্পোনেন্টের আপডেট রেজিস্টার করে যেগুলি এটি
ম্যানেজ করে। কোনও ডিভাইসে একাধিক পরিষেবা প্রদানকারী একই
কম্পোনেন্টের জন্য আপডেট প্রকাশ করলে, ক্লায়েন্ট অ্যাপগুলি এন্টারপ্রাইজ
অডিটিংয়ের জন্য সর্বোচ্চ উপলভ্য প্যাচ লেভেল (fetchAvailableSecurityPatchLevel() ব্যবহার করে) মূল্যায়ন করে অথবা আলাদা আলাদা
UpdateInfo রেকর্ড (queryAllAvailableUpdates() ব্যবহার করে) পরীক্ষা করে। আপনার পরিষেবা যেন সবসময় আপনার কম্পোনেন্টের জন্য
ক্যাননিকাল ফর্ম্যাট প্রকাশ করে (DateBasedSecurityPatchLevel
COMPONENT_SYSTEM-এর জন্য)।
আপনার আপডেট করা ক্লায়েন্টকে অনবোর্ড করার ধাপে-ধাপে গাইড
আপনার আপডেট ক্লায়েন্টে AndroidX Security State Provider লাইব্রেরি ইন্টিগ্রেট করতে এবং আপনার ডিভাইসের নিরাপত্তা আপডেট উপলভ্যতার তথ্য প্রকাশ করা শুরু করতে, এইসব ধাপ অনুসরণ করুন।
ধাপ ১: ডিপেন্ডেন্সি যোগ করুন
আপডেট প্রদানকারী প্রয়োগ করতে, আপনার প্রোজেক্টে Google
Maven রিপোজিটরি আছে কিনা দেখুন, তারপর আপনার
মডিউলের build.gradle.kts (Kotlin DSL) বা build.gradle (Groovy DSL) ফাইলে security-state-provider লাইব্রেরি যোগ করুন:
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")
}
গ্ৰুভি
// 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'
}
ধাপ ২: আপনার ম্যানিফেস্টে আপডেট পরিষেবা ঘোষণা করুন
আপনার অ্যাপের AndroidManifest.xml-এ আপনার পরিষেবা ঘোষণা করুন, সাথে
<intent-filter> ম্যাচিং
androidx.security.state.provider.UPDATE_INFO_SERVICE থাকতে হবে। পরিষেবাটিকে অবশ্যই
এক্সপোর্ট (android:exported="true") করতে হবে এবং সিঙ্গেল-ইউজার পরিষেবা
(android:singleUser="true") হিসেবে কনফিগার করতে হবে, যাতে ক্লায়েন্ট লাইব্রেরি
প্রসেস ও ব্যবহারকারীর সীমানা জুড়ে, বিশেষ করে অফিস প্রোফাইলের জন্য, এর সাথে বাইন্ড করতে পারে:
<!-- 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>
আপনার আপডেটার android.uid.system হিসেবে রান না করলে, আপনার ম্যানিফেস্টে নিম্নলিখিত
অনুমতিগুলিও ঘোষণা করুন এবং সেগুলি আপনার বিশেষ
অনুমতি সাদা তালিকায় যোগ করতে ভুলবেন না:
READ_PRIVILEGED_PHONE_STATE: ক্লায়েন্টদের আপনার প্রদানকারীর উপর ভরসা করার জন্য এটি প্রয়োজন।INTERACT_ACROSS_USERS:android:singleUser="true"-এর জন্য প্রয়োজন।
ধাপ ৩: UpdateInfoService প্রয়োগ করা
আপনার আপডেট স্ট্যাটাস প্রকাশ করতে, আপনাকে অবশ্যই UpdateInfoService
ক্লাস প্রয়োগ করতে হবে এবং প্রত্যাশিত ডেটা মডেলের সাথে মেলে এমন আপডেট রেকর্ড তৈরি করতে হবে।
UpdateInfo ডেটা মডেল স্পেসিফিকেশন
আপনি পুশ মডেল বা পুল মডেল বেছে নিন না কেন, নিম্নলিখিত
স্পেসিফিকেশন অনুযায়ী UpdateInfo
UpdateInfo.Builder ব্যবহার করে রেকর্ড তৈরি করুন:
| ফিল্ডের নাম | Getter Method | ডেটার ধরন | যাচাইকরণ ও ফর্ম্যাট সংক্রান্ত প্রয়োজনীয়তা | উদ্দেশ্য ও সিস্টেম সিম্যান্টিক্স |
|---|---|---|---|---|
component |
getComponent() |
String (@Component) |
SecurityPatchState-এ ক্যাননিকাল কনস্ট্যান্ট: COMPONENT_SYSTEM, COMPONENT_SYSTEM_MODULES অথবা COMPONENT_KERNEL. |
এই আপডেট যে সফ্টওয়্যার বা ফার্মওয়্যার সাবসিস্টেমকে টার্গেট করে তা শনাক্ত করে। |
securityPatchLevel |
getSecurityPatchLevel() |
SecurityPatchLevel |
DateBasedSecurityPatchLevel (YYYY-MM-DD) বা VersionedSecurityPatchLevel (major.minor.patch)-এর কোনও একটির ইনস্ট্যান্স হতে হবে অথবা SecurityPatchState.getComponentSecurityPatchLevel() ব্যবহার করে পার্স করা হতে হবে। |
এই আপডেট ইনস্টল করার পরে যে টার্গেট নিরাপত্তা প্যাচ লেভেল পাওয়া যাবে। |
publishedDateMillis |
getPublishedDateMillis() |
long |
Unix epoch (System.currentTimeMillis()) থেকে মিলিসেকেন্ড। অবশ্যই > 0 হতে হবে। |
OTA রিলিজের সময় সহ ব্যবহারকারীদের জন্য আপডেট উপলভ্য করা হলে। পেলোড ডাউনলোড বা ইনস্টল করার সময় ব্যবহার করবেন না। |
lastCheckTimeMillis |
getLastCheckTimeMillis() |
long |
Unix epoch থেকে মিলিসেকেন্ডে। > 0 হতে হবে। |
সিঙ্ক্রোনাইজেশনের সময় আপনার পরিষেবা প্রদানকারী এই আপডেট রেকর্ড যাচাই বা খুঁজে পাওয়ার টাইমস্ট্যাম্প। |
নিচের বিকল্পগুলি থেকে আপনার আপডেটারের আর্কিটেকচারের সাথে মানানসই ডেলিভারি মডেল বেছে নিন:
বিকল্প ক: পুশ মডেল (সাজেস্ট করা হয়েছে)
আপনার ব্যাকগ্রাউন্ড সিঙ্ক ওয়ার্কার OTA সার্ভার চেক করার সময়, নিশ্চিত করুন যে কোনও
আবিষ্কৃত আপডেট ডিভাইসের বর্তমান প্যাচ লেভেলকে উন্নত করে এবং UpdateInfoManager.registerUpdate() ব্যবহার করে
সেটি সেভ করে অথবা কোনও উন্নত নিরাপত্তা আপডেট
পেন্ডিং না থাকলে UpdateInfoManager.unregisterUpdate() কল করে। প্রতিটি সিঙ্ক করার শেষে সবসময় UpdateInfoManager.setLastCheckTimeMillis()-এ কল করুন (এমনকি registerUpdate()-এ কল করার পরেও, যা
প্রতি-কম্পোনেন্ট UpdateInfo রেকর্ড বজায় রাখে কিন্তু ক্লায়েন্টকে ফেরত দেওয়া গ্লোবাল শেষ-চেক
টাইমস্ট্যাম্প আপডেট করে না)। আপনি এটি Kotlin-এ WorkManager CoroutineWorker অথবা Java-তে Worker-এর মাধ্যমে প্রয়োগ করতে পারেন:
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()
}
}
জাভা
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();
}
}
পুশ-ভিত্তিক মডেলে, ব্যাকগ্রাউন্ড টাস্ক সরাসরি
UpdateInfoManager-এ আপডেট রেকর্ড সেভ করে। ফ্রেমওয়ার্ককে সবসময় লোকাল ডিস্ক স্টোরেজ থেকে রেকর্ড পরিবেশন করার
নির্দেশ দিতে, shouldFetchUpdates() ওভাররাইড করে false রিটার্ন করুন, এটি Kotlin-এ
UpdateInfoService অথবা Java-তে
ListenableFutureUpdateInfoService এক্সটেন্ড করে করুন:
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()
}
জাভা
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());
}
}
বিকল্প খ: পুল মডেল (অন-ডিমান্ড)
পুল-ভিত্তিক আর্কিটেকচারে, আপনার পরিষেবা অন-ডিমান্ড রিফ্রেশ করার অনুরোধগুলি হ্যান্ডেল করে যা ক্লায়েন্ট অ্যাপগুলি দ্বারা ট্রিগার করা হয় যখন স্থানীয় ক্যাশে পুরানো হয়ে যায়।
চাহিদা মতো আপডেট কোয়েরি ম্যানেজ করতে, Kotlin-এ UpdateInfoService এক্সটেন্ড করুন
(সাসপেন্ড করা fetchUpdates() ফাংশন প্রয়োগ করা) অথবা
Java-তে ListenableFutureUpdateInfoService (fetchUpdatesAsync() প্রয়োগ করা
Guava ListenableFuture রিটার্ন করা):
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)
}
}
জাভা
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);
}
}
ধাপ ৪: ডিভাইস রিবুট করার পরে প্রয়োগ করা আপডেট মুছে দিন
UpdateInfoManager অটোমেটিক পুরনো আপডেট সরিয়ে দেয় যখনই
registerUpdate() চালু করা হয়, আপনার আপডেটার OTA আপডেট ইনস্টল করা হয়ে গেলে
registerUpdate() আবার কল করবে না যতক্ষণ না পরবর্তী
শিডিউল করা সার্ভার সিঙ্ক চক্র শুরু হয়। রিবুট করার সাথে সাথেই ক্লায়েন্ট অ্যাপ যাতে আগে থেকেই ইনস্টল করা আপডেটকে
পেন্ডিং হিসেবে না দেখে, তার জন্য
ACTION_BOOT_COMPLETED শুনুন এবং OTA আপডেট ইনস্টল করা হয়ে গেলে
UpdateInfoManager.unregisterUpdate() কল করুন
যাতে লোকাল ক্যাশে থেকে রেকর্ড মুছে দেওয়া যায়। এটি শুধুমাত্র তখনই করা হয় যখন কোনও
আপডেট ইনস্টল করা হয়ে যায়। এর ফলে, প্রতিটি সাধারণ ডিভাইস রিবুটের সময়, শর্তহীনভাবে বাকি থাকা
(আনইনস্টল করা) আপডেট মুছে ফেলা এড়ানো যায়। কারণ,
UpdateInfoManager কী কম্পোনেন্ট
অনুযায়ী রেকর্ড আপডেট করে, তাই আনরেজিস্টার করার জন্য
UpdateInfo অবজেক্ট তৈরি করার সময় আপনাকে শুধু টার্গেট কম্পোনেন্ট নির্দিষ্ট করতে হবে:
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())
জাভা
// 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());
ধাপ ৫: আপনার ইন্টিগ্রেশন যাচাই করুন
এন্ড-টু-এন্ড ইন্টিগ্রেশন যাচাই করতে এবং সাধারণ OEM ডেপ্লয়মেন্ট সংক্রান্ত সমস্যা এড়াতে, Android Debug Bridge (ADB) ব্যবহার করে Android ডিভাইস বা এমুলেটরে নিম্নলিখিত চেকগুলি চালান:
যাচাই করুন যে ক্লায়েন্ট আপনার পরিষেবা প্রদানকারীকে বিশ্বাস করে কিনা: ক্লায়েন্ট অ্যাপ এমন কোনও পরিষেবা প্রদানকারীকে উপেক্ষা করে যার কাছে
READ_PRIVILEGED_PHONE_STATEনেই, এমনকি সেটি আগে থেকে ইনস্টল করা থাকলেও। অনুমতি দেওয়া হয়েছে কিনা তা কনফার্ম করুন:adb shell dumpsys package <your_package_name> | grep "READ_PRIVILEGED_PHONE_STATE: granted=true"তারপরে কনফার্ম করুন যে আপনার পরিষেবা খুঁজে পাওয়া যাচ্ছে এবং এতে কোনও পরিষেবার অনুমতি নেই। আপনার পরিষেবার আউটপুটে,
exported=trueএবংpermission=nullচেক করুন:adb shell pm query-services --user 0 -a androidx.security.state.provider.UPDATE_INFO_SERVICEকোনও ক্লায়েন্ট এখনও আপনার প্রদানকারীকে দেখতে না পেলে,
SecurityPatchStateট্যাগ থেকেIgnoring untrusted update provider-এর জন্য logcat চেক করুন।ব্যবহারকারী ০ ও অফিসের প্রোফাইল, দুটিতেই ইনটেন্ট রেজোলিউশন যাচাই করুন: নিশ্চিত করুন যে Android OS
PackageManagerআপনার এক্সপোর্ট করাUPDATE_INFO_SERVICEইনটেন্ট ফিল্টারকে প্রাথমিক ব্যবহারকারী (User 0) ও যেকোনও অ্যাক্টিভ Android Enterprise অফিসের প্রোফাইল (যেমনUser 10), দুটিতেই সমাধান করে: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_SERVICEdumpsysব্যবহার করে পরিষেবার স্ট্যাটাস ও ক্যাশে করা রেকর্ড যাচাই করুন:UpdateInfoServiceওভাররাইড করেdump()Global Last Check,Should Throttle(রেট লিমিটার-এর স্ট্যাটাস) এবংCached Updatesসম্পর্কে অভিযোগ জানান। কারণUpdateInfoServiceহল একটি বাউন্ড পরিষেবা এবং কোয়েরি করার সাথে সাথেই ক্লায়েন্ট আনবাইন্ড হয়ে যায়,dumpsys activity serviceকোনও ক্লায়েন্ট বাইন্ড না থাকলে(nothing)আউটপুট দেয়।dumpsysচালানোর আগে পরিষেবাটি স্পষ্টভাবে চালু করুন: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>ডায়াগনস্টিক আউটপুটের উদাহরণ:
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ক্লায়েন্ট বাইন্ডিং ট্রিগার করুন এবং টেলিমেট্রি ফলাফল যাচাই করুন: কোনও প্রিভিলেজ না থাকা টেস্ট অ্যাপ (সিস্টেম সিগনেচার পারমিশন নেই) থেকে,
SecurityPatchState.queryAllAvailableUpdates()ইনভোক করুন। আপনি যদি টেলিমেট্রি কলব্যাক প্রয়োগ করে থাকেন, তাহলে নিম্নলিখিত বিষয়গুলি চেক করুন:- যাচাই করুন যে অপ্রিভিলেজড ক্লায়েন্ট
SecurityExceptionছাড়া বাইন্ড করে এবংonClientConnected(packageName, callerUid)ট্রিগার করে। - পুশ-মডেল প্রদানকারীর (
shouldFetchUpdates() == false) জন্য: নিশ্চিত করুন যেonRequestCompleted(telemetry)প্রতিটি কোয়েরিতেfetchDurationMillis == 0সহUpdateFetchOutcome.CACHE_HIT(1) লগ করে। - পুল-মডেল প্রদানকারীর (
shouldFetchUpdates() == true) জন্য: যাচাই করুন যেonRequestCompleted(telemetry)প্রাথমিক বাসি-ক্যাশে কোয়েরিতেUpdateFetchOutcome.FETCHED(3) লগ করা হয়েছে, তারপরেCACHE_HIT(1) অবিলম্বে পরবর্তী কোয়েরিতে লগ করা হয়েছে। (পুল-মডেল টেস্ট রান চলাকালীন ১-ঘণ্টার পারসিস্ট্যান্ট রেট লিমিটার রিসেট করতে,adb shell pm clear <your_package_name>রান করান।)
- যাচাই করুন যে অপ্রিভিলেজড ক্লায়েন্ট
ঐচ্ছিক ও উন্নত কনফিগারেশন
ক্যাশিং নীতি ও রেট লিমিটিং
ক্লায়েন্ট আপডেট সংক্রান্ত কোয়েরি করলে, UpdateInfoService ব্যাকএন্ড সার্ভারের
উপর লোড কমানোর জন্য ডেটা ফ্রেশনেস ব্যালেন্স করতে
ডবল-চেক করা লকিং ওয়ার্কফ্লো এক্সিকিউট করে:

- দ্রুত পাথ (
shouldFetchUpdates()): ডিফল্ট হিসেবে, গ্লোবালlastCheckTimeMillis১ ঘণ্টার বেশি পুরনো হলে (TimeUnit.HOURS.toMillis(1)) তবেইshouldFetchUpdates()true(পুরনো ক্যাশে আছে তা বোঝায়) রিটার্ন করে।shouldFetchUpdates()falseরিটার্ন করলে, পরিষেবা অবিলম্বে লক না নিয়ে বা নেটওয়ার্ক I/O না করেই ফলাফলUpdateFetchOutcome.CACHE_HITসহ ক্যাশে করা রেকর্ড রিটার্ন করে। এই ক্যাশিং নীতি কাস্টমাইজ করতে আপনিshouldFetchUpdates()ওভাররাইড করতে পারবেন। - স্লো পাথ ও রিকোয়েস্ট কোয়ালিসিং:
shouldFetchUpdates()trueরিটার্ন করলে, পরিষেবা একটি ইন্টার্নাল কোরাউটিন মিউটেক্স অ্যাকোয়ার করে এবংshouldFetchUpdates()আবার মূল্যায়ন করে (লকের জন্য অপেক্ষা করার সময় কোনও কনকারেন্ট অনুরোধ ইতিমধ্যেই ক্যাশে রিফ্রেশ করে থাকলেUpdateFetchOutcome.COALESCEDরিটার্ন করে)। - অবিরাম রেট লিমিটার (
shouldThrottle()): কোয়েরি বার্স্ট বা বারবার ব্যর্থতা থেকে ব্যাকএন্ড ইনফ্রাস্ট্রাকচারকে রক্ষা করতে,shouldThrottle()অ্যাপ ও ডিভাইস রিস্টার্ট জুড়ে অবিরাম ন্যূনতম ১-ঘণ্টার ব্যবধান এনফোর্স করে।UpdateInfoServicefetchUpdates()ইনভোক করার আগে প্রতিটি প্রচেষ্টা রেকর্ড করে, তাইfetchUpdates()কোনও ব্যতিক্রম ঘটলে (onFetchFailed(e)ইনভোক করার পরেUpdateFetchOutcome.FAILEDরিটার্ন করলে), পরবর্তী ৬০ মিনিট ধরে পরবর্তী কোয়েরিগুলি ফলাফলUpdateFetchOutcome.THROTTLEDসহ ক্যাশে করা ফলব্যাক ডেটা রিটার্ন করে।
পর্যবেক্ষণযোগ্যতা, টেলিমেট্রি ও ডায়াগনস্টিক
UpdateInfoService ক্লায়েন্ট
অ্যাডপশন ট্র্যাক করা, IPC লেটেন্সি মনিটর করা এবং লো-লেভেল AIDL স্টাব ইনস্ট্রুমেন্ট না করেই
ব্যাকএন্ড সংক্রান্ত সমস্যার লগ করার জন্য বিল্ট-ইন অবজার্ভেবিলিটি হুক প্রদান করে:
onRequestCompleted(telemetry):UpdateCheckTelemetryসারসংক্ষেপ সহ প্রতিটি আপডেট চেক সম্পূর্ণ হওয়ার পরে আহ্বান করা হয়।onClientConnected(packageName, callerUid): কোনও যাচাই করা ক্লায়েন্ট সেশন খুললে এটি ইনভোক করা হয়।onClientDisconnected(packageName, callerUid): কোনও ক্লায়েন্ট আনবাইন্ড করলে বা তার প্রসেস বন্ধ হয়ে গেলে এটি ইনভোক করা হয়।onFetchFailed(e): কোনও ব্যতিক্রম ঘটলে ইনভোক করা হয়fetchUpdates(), পরিষেবা ক্যাশে করা ফলব্যাক ডেটা রিটার্ন করার আগে।
UpdateInfoService (Kotlin) বা
ListenableFutureUpdateInfoService (Java)-তে এইসব কলব্যাক ওভাররাইড করুন:
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)
}
}
জাভা
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);
}
}
টেলিমেট্রি ফলাফল ও লেটেন্সি মেট্রিক
UpdateCheckTelemetry মনোটনিক অতিবাহিত সময় পরিমাপ করে
(SystemClock.elapsedRealtime()) এবং UpdateFetchOutcome-এ বর্ণিত পাঁচটি ফলাফলের
মধ্যে একটি রিপোর্ট করে:
| ফলাফল ধ্রুবক | @IntDef কোড |
লগ করা মেট্রিক প্রপার্টি | বিবরণ ও সিস্টেমের অবস্থা |
|---|---|---|---|
UpdateFetchOutcome.CACHE_HIT |
1 |
totalDurationMillis, processingDurationMillis, callerUid |
দ্রুত পাথ থেকে লোকাল ডিস্ক/মেমরি ক্যাশে থেকে সঙ্গে সঙ্গে পরিবেশন করা হয় (shouldFetchUpdates() ফেরত দেওয়া হয়েছে false)। lockWaitDurationMillis ও fetchDurationMillis হল 0। |
UpdateFetchOutcome.COALESCED |
2 |
totalDurationMillis, lockWaitDurationMillis, processingDurationMillis, callerUid |
আরেকটি অ্যাক্টিভ রিফ্রেশের পিছনে কোয়েরি সারিবদ্ধ করা হয়েছে; লক পাওয়ার পরে, ডেটা ফ্রেশ ছিল। ডুপ্লিকেট নেটওয়ার্ক ফেচ এড়ানো হয়েছে (fetchDurationMillis হল 0)। |
UpdateFetchOutcome.FETCHED |
3 |
totalDurationMillis, lockWaitDurationMillis, processingDurationMillis, fetchDurationMillis, callerUid |
ব্যাকএন্ড নেটওয়ার্ক সিঙ্ক সফলভাবে এক্সিকিউট করা হয়েছে (fetchUpdates() সম্পূর্ণ হয়েছে)। নতুন রেকর্ড ডিস্কে সেভ করা হয়েছে। |
UpdateFetchOutcome.THROTTLED |
4 |
totalDurationMillis, lockWaitDurationMillis, processingDurationMillis, callerUid |
রেট লিমিটারের মাধ্যমে অনুরোধ ব্লক করা হয়েছে (shouldThrottle() ফেরত দেওয়া হয়েছে true)। ক্লায়েন্টকে নিরাপদে ক্যাশে করা ডেটা ফেরত দেওয়া হয়েছে (fetchDurationMillis হল 0)। |
UpdateFetchOutcome.FAILED |
5 |
totalDurationMillis, lockWaitDurationMillis, processingDurationMillis, fetchDurationMillis, callerUid |
আপডেট চেক বা নেটওয়ার্ক অনুরোধে ব্যতিক্রম ঘটেছে। ব্যতিক্রম ফায়ারওয়াল দ্বারা ধরা পড়েছে, onFetchFailed(e) ফায়ার করেছে, ক্যাশে করা ফলব্যাক রিটার্ন করেছে। |
উন্নত পরিষেবা ব্রোকার হুক: getCallerUid()
কোনও ক্লায়েন্ট কানেক্ট করলে, UpdateInfoService প্রাথমিক বাইন্ডার থ্রেডে (fetchUpdates()-এর আগে
Binder.clearCallingIdentity() কল করার আগে) অটোমেটিক
getCallerUid() মূল্যায়ন করে, প্যাকেজ মালিকানা যাচাই করে এবং যাচাই করা কলার UID সরাসরি
onRequestCompleted(telemetry)-এ
onClientConnected(), onClientDisconnected() ও telemetry.callerUid-এ পাস করে।
স্ট্যান্ডার্ড Android <service> কম্পোনেন্টের জন্য, আপনাকে কল করতে বা ওভাররাইড করতে হবে না
getCallerUid()। protected open getCallerUid() পদ্ধতি (যা ডিফল্ট হিসেবে
Binder.getCallingUid()-এর কাছে ডেলিগেট করে) হোস্ট অ্যাপের জন্য ওভাররাইড হুক হিসেবে
প্রদান করা হয় যা ইন্টার্নাল সার্ভিস ব্রোকার বা
প্রক্সি আর্কিটেকচারের মাধ্যমে Binder IPC রাউট করে, এর ফলে সাবক্লাস ব্রোকারের UID-এর
পরিবর্তে লজিক্যাল ক্লায়েন্ট UID রিটার্ন করতে পারে।
অতিরিক্ত রিসোর্স
প্রকাশনার নিরাপত্তা সংক্রান্ত অবস্থা সম্পর্কে আরও জানতে, নিম্নলিখিত রিসোর্স দেখুন:
ডকুমেন্টেশন
- ডিভাইসের নিরাপত্তা সংক্রান্ত অবস্থা বোঝা
- Android নিরাপত্তা বুলেটিন
- মডুলার সিস্টেম কম্পোনেন্ট
- অতিরিক্ত নিরাপত্তা প্যাচ
- Security State Provider 1.0.0 রিলিজ নোট
API রেফারেন্স
UpdateInfoServiceListenableFutureUpdateInfoServiceUpdateInfoManagerUpdateInfoUpdateInfo.BuilderUpdateCheckTelemetryUpdateFetchOutcomeSecurityPatchState