خدمة الملء التلقائي هي تطبيق يسهّل على المستخدمين ملء النماذج من خلال إدخال البيانات في طرق عرض التطبيقات الأخرى. يمكن لخدمات الملء التلقائي أيضًا استرداد بيانات المستخدم من طرق العرض في أحد التطبيقات وتخزينها لاستخدامها في وقت لاحق. عادةً ما يتم توفير خدمات الملء التلقائي من خلال التطبيقات التي تدير بيانات المستخدمين، مثل مدراء كلمات المرور.
يسهّل Android ملء النماذج من خلال إطار عمل الملء التلقائي المتاح في الإصدار 8.0 من Android (المستوى 26 من واجهة برمجة التطبيقات) والإصدارات الأحدث. يمكن للمستخدمين الاستفادة من ميزات الملء التلقائي فقط في حال توفّر تطبيق يقدّم خدمات الملء التلقائي على أجهزتهم.
توضّح هذه الصفحة كيفية تنفيذ خدمة الملء التلقائي في تطبيقك. إذا كنت تبحث عن عينة تعليمات برمجية توضّح كيفية تنفيذ خدمة، يمكنك الاطّلاع على عينة AutofillFramework بلغة Java أو Kotlin. لمزيد من التفاصيل حول آلية عمل خدمات الملء التلقائي،
يُرجى الاطّلاع على الصفحات المرجعية لفئتَي AutofillService وAutofillManager.
بيانات البيان والأذونات
يجب أن تتضمّن التطبيقات التي توفّر خدمات الملء التلقائي بيانًا يوضّح
كيفية تنفيذ الخدمة. لتحديد البيان، أدرِج عنصر <service> في بيان التطبيق. يجب أن يتضمّن العنصر <service> السمات والعناصر التالية:
- سمة
android:nameالتي تشير إلى الفئة الفرعية منAutofillServiceفي التطبيق الذي ينفّذ الخدمة. - سمة
android:permissionالتي تعلن عن إذنBIND_AUTOFILL_SERVICE - عنصر
<intent-filter>الذي يحدّد عنصره الثانوي الإلزامي<action>الإجراءandroid.service.autofill.AutofillService. - عنصر
<meta-data>اختياري يمكنك استخدامه لتوفير مَعلمات ضبط إضافية للخدمة
يوضّح المثال التالي بيان خدمة الملء التلقائي:
<service
android:name=".MyAutofillService"
android:label="My Autofill Service"
android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
<meta-data
android:name="android.autofill"
android:resource="@xml/service_configuration" />
</service>
يتضمّن العنصر <meta-data> السمة android:resource التي تشير إلى مورد XML يحتوي على تفاصيل إضافية حول الخدمة. يحدّد المورد service_configuration في المثال السابق نشاطًا يسمح للمستخدمين بضبط الخدمة. يوضّح المثال التالي مورد XML لـ service_configuration:
<autofill-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.android.SettingsActivity" />
لمزيد من المعلومات حول موارد XML، يُرجى الاطّلاع على نظرة عامة على موارد التطبيق.
طلب تفعيل الخدمة
يتم استخدام التطبيق كخدمة ملء تلقائي بعد أن يطلب إذن
BIND_AUTOFILL_SERVICE ويمنحه المستخدم في إعدادات الجهاز. يمكن لأي تطبيق التحقّق مما إذا كان هو الخدمة المفعَّلة من خلال استدعاء طريقة hasEnabledAutofillServices() من فئة AutofillManager.
إذا لم يكن التطبيق هو خدمة الملء التلقائي الحالية، يمكنه أن يطلب من المستخدم
تغيير إعدادات الملء التلقائي باستخدام
الطلب ACTION_REQUEST_SET_AUTOFILL_SERVICE. يعرض الغرض قيمة RESULT_OK إذا اختار المستخدم خدمة ملء تلقائي تتطابق مع حزمة المتصل.
ملء طرق عرض العميل
تتلقّى خدمة الملء التلقائي طلبات لملء طرق عرض العميل عندما يتفاعل المستخدم مع تطبيقات أخرى. إذا كانت خدمة الملء التلقائي تتضمّن بيانات مستخدم تستوفي الطلب، فإنّها ترسل البيانات في الاستجابة. يعرض نظام Android واجهة مستخدم للملء التلقائي مع البيانات المتاحة، كما هو موضّح في الشكل 1:
يحدّد إطار عمل الملء التلقائي سير عمل لملء طرق العرض المصمّمة لتقليل الوقت الذي يرتبط فيه نظام Android بخدمة الملء التلقائي. في كل طلب، يرسل نظام Android عنصر AssistStructure إلى الخدمة من خلال استدعاء طريقة onFillRequest().
تتحقّق خدمة الملء التلقائي مما إذا كان بإمكانها تلبية الطلب باستخدام بيانات المستخدمين
التي سبق تخزينها. إذا كان بإمكان الخدمة تلبية الطلب، فإنّها تجمّع البيانات في عناصر Dataset. تستدعي الخدمة الإجراء onSuccess()، وتمرّر عنصر FillResponse يحتوي على عناصر Dataset. إذا لم تتوفّر لدى الخدمة بيانات لتلبية الطلب،
ستمرّر null إلى طريقة onSuccess(). تستدعي الخدمة طريقة onFailure() بدلاً من ذلك في حال حدوث خطأ أثناء معالجة الطلب.
للحصول على شرح مفصّل لسير العمل، يُرجى الاطّلاع على الوصف في صفحة مرجع
AutofillService.
يعرض الرمز التالي مثالاً على طريقة onFillRequest():
Kotlin
override fun onFillRequest(
request: FillRequest,
cancellationSignal: CancellationSignal,
callback: FillCallback
) {
// Get the structure from the request
val context: List<FillContext> = request.fillContexts
val structure: AssistStructure = context[context.size - 1].structure
// Traverse the structure looking for nodes to fill out
val parsedStructure: ParsedStructure = parseStructure(structure)
// Fetch user data that matches the fields
val (username: String, password: String) = fetchUserData(parsedStructure)
// Build the presentation of the datasets
val usernamePresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1)
usernamePresentation.setTextViewText(android.R.id.text1, "my_username")
val passwordPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1)
passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username")
// Add a dataset to the response
val fillResponse: FillResponse = FillResponse.Builder()
.addDataset(Dataset.Builder()
.setValue(
parsedStructure.usernameId,
AutofillValue.forText(username),
usernamePresentation
)
.setValue(
parsedStructure.passwordId,
AutofillValue.forText(password),
passwordPresentation
)
.build())
.build()
// If there are no errors, call onSuccess() and pass the response
callback.onSuccess(fillResponse)
}
data class ParsedStructure(var usernameId: AutofillId, var passwordId: AutofillId)
data class UserData(var username: String, var password: String)
Java
@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) {
// Get the structure from the request
List<FillContext> context = request.getFillContexts();
AssistStructure structure = context.get(context.size() - 1).getStructure();
// Traverse the structure looking for nodes to fill out
ParsedStructure parsedStructure = parseStructure(structure);
// Fetch user data that matches the fields
UserData userData = fetchUserData(parsedStructure);
// Build the presentation of the datasets
RemoteViews usernamePresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
usernamePresentation.setTextViewText(android.R.id.text1, "my_username");
RemoteViews passwordPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username");
// Add a dataset to the response
FillResponse fillResponse = new FillResponse.Builder()
.addDataset(new Dataset.Builder()
.setValue(parsedStructure.usernameId,
AutofillValue.forText(userData.username), usernamePresentation)
.setValue(parsedStructure.passwordId,
AutofillValue.forText(userData.password), passwordPresentation)
.build())
.build();
// If there are no errors, call onSuccess() and pass the response
callback.onSuccess(fillResponse);
}
class ParsedStructure {
AutofillId usernameId;
AutofillId passwordId;
}
class UserData {
String username;
String password;
}
يمكن أن تتضمّن الخدمة أكثر من مجموعة بيانات واحدة تستوفي الطلب. في هذه الحالة، يعرض نظام Android خيارات متعددة، خيار واحد لكل مجموعة بيانات، في واجهة مستخدم الملء التلقائي. يوضّح مثال الرمز التالي كيفية تقديم مجموعات بيانات متعددة في ردّ:
Kotlin
// Add multiple datasets to the response
val fillResponse: FillResponse = FillResponse.Builder()
.addDataset(Dataset.Builder()
.setValue(parsedStructure.usernameId,
AutofillValue.forText(user1Data.username), username1Presentation)
.setValue(parsedStructure.passwordId,
AutofillValue.forText(user1Data.password), password1Presentation)
.build())
.addDataset(Dataset.Builder()
.setValue(parsedStructure.usernameId,
AutofillValue.forText(user2Data.username), username2Presentation)
.setValue(parsedStructure.passwordId,
AutofillValue.forText(user2Data.password), password2Presentation)
.build())
.build()
Java
// Add multiple datasets to the response
FillResponse fillResponse = new FillResponse.Builder()
.addDataset(new Dataset.Builder()
.setValue(parsedStructure.usernameId,
AutofillValue.forText(user1Data.username), username1Presentation)
.setValue(parsedStructure.passwordId,
AutofillValue.forText(user1Data.password), password1Presentation)
.build())
.addDataset(new Dataset.Builder()
.setValue(parsedStructure.usernameId,
AutofillValue.forText(user2Data.username), username2Presentation)
.setValue(parsedStructure.passwordId,
AutofillValue.forText(user2Data.password), password2Presentation)
.build())
.build();
يمكن لخدمات الملء التلقائي التنقّل بين عناصر ViewNode في
AssistStructure لاسترداد بيانات الملء التلقائي المطلوبة لتلبية الطلب.
يمكن للخدمة استرداد بيانات الملء التلقائي باستخدام طرق من فئة ViewNode، مثل getAutofillId().
يجب أن تكون الخدمة قادرة على وصف محتوى العرض للتحقّق مما إذا كان بإمكانها تلبية الطلب. يُعدّ استخدام السمة autofillHints هو الطريقة الأولى التي يجب أن تستخدمها الخدمة لوصف محتوى طريقة العرض. ومع ذلك، يجب أن توفّر تطبيقات العميل السمة بشكل صريح في طرق العرض قبل أن تصبح متاحة للخدمة.
إذا لم يوفّر تطبيق العميل السمة autofillHints، يجب أن تستخدم الخدمة أساليبها الإرشادية لوصف المحتوى. يمكن للخدمة استخدام طرق من فئات أخرى، مثل getText() أو getHint()، للحصول على معلومات حول محتوى العرض. لمزيد من المعلومات، يُرجى الاطّلاع على تقديم تلميحات للملء التلقائي.
يوضّح المثال التالي كيفية التنقّل في AssistStructure واسترداد بيانات الملء التلقائي من عنصر ViewNode:
Kotlin
fun traverseStructure(structure: AssistStructure) {
val windowNodes: List<AssistStructure.WindowNode> =
structure.run {
(0 until windowNodeCount).map { getWindowNodeAt(it) }
}
windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
val viewNode: ViewNode? = windowNode.rootViewNode
traverseNode(viewNode)
}
}
fun traverseNode(viewNode: ViewNode?) {
if (viewNode?.autofillHints?.isNotEmpty() == true) {
// If the client app provides autofill hints, you can obtain them using
// viewNode.getAutofillHints();
} else {
// Or use your own heuristics to describe the contents of a view
// using methods such as getText() or getHint()
}
val children: List<ViewNode>? =
viewNode?.run {
(0 until childCount).map { getChildAt(it) }
}
children?.forEach { childNode: ViewNode ->
traverseNode(childNode)
}
}
Java
public void traverseStructure(AssistStructure structure) {
int nodes = structure.getWindowNodeCount();
for (int i = 0; i < nodes; i++) {
WindowNode windowNode = structure.getWindowNodeAt(i);
ViewNode viewNode = windowNode.getRootViewNode();
traverseNode(viewNode);
}
}
public void traverseNode(ViewNode viewNode) {
if(viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) {
// If the client app provides autofill hints, you can obtain them using
// viewNode.getAutofillHints();
} else {
// Or use your own heuristics to describe the contents of a view
// using methods such as getText() or getHint()
}
for(int i = 0; i < viewNode.getChildCount(); i++) {
ViewNode childNode = viewNode.getChildAt(i);
traverseNode(childNode);
}
}
حفظ بيانات المستخدمين
تحتاج خدمة الملء التلقائي إلى بيانات المستخدم لملء طرق العرض في التطبيقات. عندما يملأ المستخدمون طريقة عرض يدويًا، يُطلب منهم حفظ البيانات في خدمة الملء التلقائي الحالية، كما هو موضّح في الشكل 2.
لحفظ البيانات، يجب أن تشير الخدمة إلى أنّها مهتمة بتخزين البيانات
لاستخدامها في المستقبل. قبل أن يرسل نظام Android طلبًا لحفظ البيانات،
هناك طلب ملء حيث تتاح للخدمة فرصة ملء
طرق العرض. للإشارة إلى أنّ الخدمة مهتمة بحفظ البيانات، تتضمّن الخدمة عنصر SaveInfo في الردّ على طلب الملء. يحتوي العنصر SaveInfo على البيانات التالية على الأقل:
- نوع بيانات المستخدم التي يتم حفظها للحصول على قائمة بقيم
SAVE_DATAالمتاحة، يُرجى الاطّلاع علىSaveInfo. - الحدّ الأدنى من طرق العرض التي يجب تغييرها لتفعيل طلب حفظ
على سبيل المثال، يتطلّب نموذج تسجيل الدخول عادةً من المستخدم تعديل
طريقتَي العرض
usernameوpasswordلتشغيل طلب حفظ.
يرتبط عنصر SaveInfo بعنصر FillResponse، كما هو موضّح في مثال الرمز البرمجي التالي:
Kotlin
override fun onFillRequest(
request: FillRequest,
cancellationSignal: CancellationSignal,
callback: FillCallback
) {
// ...
// Builder object requires a non-null presentation
val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1)
val fillResponse: FillResponse = FillResponse.Builder()
.addDataset(
Dataset.Builder()
.setValue(parsedStructure.usernameId, null, notUsed)
.setValue(parsedStructure.passwordId, null, notUsed)
.build()
)
.setSaveInfo(
SaveInfo.Builder(
SaveInfo.SAVE_DATA_TYPE_USERNAME or SaveInfo.SAVE_DATA_TYPE_PASSWORD,
arrayOf(parsedStructure.usernameId, parsedStructure.passwordId)
).build()
)
.build()
// ...
}
Java
@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) {
// ...
// Builder object requires a non-null presentation
RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
FillResponse fillResponse = new FillResponse.Builder()
.addDataset(new Dataset.Builder()
.setValue(parsedStructure.usernameId, null, notUsed)
.setValue(parsedStructure.passwordId, null, notUsed)
.build())
.setSaveInfo(new SaveInfo.Builder(
SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
new AutofillId[] {parsedStructure.usernameId, parsedStructure.passwordId})
.build())
.build();
// ...
}
يمكن لخدمة الملء التلقائي تنفيذ منطق للاحتفاظ ببيانات المستخدم في طريقة onSaveRequest()، والتي يتم استدعاؤها عادةً بعد انتهاء نشاط العميل أو عندما يستدعي تطبيق العميل commit(). يعرض الرمز التالي مثالاً على طريقة onSaveRequest():
Kotlin
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
// Get the structure from the request
val context: List<FillContext> = request.fillContexts
val structure: AssistStructure = context[context.size - 1].structure
// Traverse the structure looking for data to save
traverseStructure(structure)
// Persist the data - if there are no errors, call onSuccess()
callback.onSuccess()
}
Java
@Override
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
// Get the structure from the request
List<FillContext> context = request.getFillContexts();
AssistStructure structure = context.get(context.size() - 1).getStructure();
// Traverse the structure looking for data to save
traverseStructure(structure);
// Persist the data - if there are no errors, call onSuccess()
callback.onSuccess();
}
يجب أن تشفّر خدمات الملء التلقائي البيانات الحسّاسة قبل حفظها. ومع ذلك، يمكن أن تتضمّن بيانات المستخدمين تصنيفات أو بيانات غير حسّاسة. على سبيل المثال، يمكن أن يتضمّن حساب المستخدم تصنيفًا يحدّد البيانات على أنّها تابعة لحساب عمل أو حساب شخصي. يجب ألا تشفّر الخدمات التصنيفات. من خلال عدم تشفير التصنيفات، يمكن للخدمات استخدام التصنيفات في طرق عرض العروض التقديمية إذا لم تتم مصادقة المستخدم. بعد ذلك، يمكن للخدمات استبدال التصنيفات بالبيانات الفعلية بعد مصادقة المستخدم.
تأجيل واجهة مستخدم حفظ الملء التلقائي
بدءًا من الإصدار Android 10، إذا كنت تستخدم شاشات متعددة لتنفيذ سير عمل الملء التلقائي، مثل شاشة واحدة لحقل اسم المستخدم وأخرى لكلمة المرور، يمكنك تأجيل واجهة مستخدم حفظ الملء التلقائي باستخدام العلامة SaveInfo.FLAG_DELAY_SAVE.
في حال ضبط هذه العلامة، لن يتم تفعيل واجهة مستخدم حفظ الملء التلقائي عند تنفيذ سياق الملء التلقائي المرتبط باستجابة SaveInfo. بدلاً من ذلك، يمكنك
استخدام نشاط منفصل ضمن المهمة نفسها لإرسال طلبات الملء المستقبلية، ثم
عرض واجهة المستخدم باستخدام طلب حفظ. لمزيد من المعلومات، يُرجى الاطّلاع على
SaveInfo.FLAG_DELAY_SAVE.
طلب مصادقة المستخدم
يمكن أن توفّر خدمات الملء التلقائي مستوى إضافيًا من الأمان من خلال مطالبة المستخدم بالمصادقة قبل أن يتمكّن من ملء طرق العرض. السيناريوهات التالية هي مرشحة جيدة لتنفيذ مصادقة المستخدم:
- يجب فتح قفل بيانات المستخدم في التطبيق باستخدام كلمة مرور أساسية أو مسح بصمة الإصبع.
- يجب فتح قفل مجموعة بيانات معيّنة، مثل تفاصيل بطاقة الائتمان باستخدام رمز التحقّق من البطاقة (CVC).
في حال كانت الخدمة تتطلّب مصادقة المستخدم قبل فتح قفل البيانات، يمكن للخدمة عرض بيانات نموذجية أو تصنيف وتحديد Intent الذي يعالج المصادقة. إذا كنت بحاجة إلى بيانات إضافية لمعالجة الطلب بعد اكتمال عملية المصادقة، يمكنك إضافة هذه البيانات إلى الغرض. يمكن بعد ذلك لنشاط المصادقة عرض البيانات إلى الفئة AutofillService في تطبيقك.
يوضّح مثال الرمز البرمجي التالي كيفية تحديد أنّ الطلب يتطلّب مصادقة:
Kotlin
val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
setTextViewText(android.R.id.text1, "requires authentication")
}
val authIntent = Intent(this, AuthActivity::class.java).apply {
// Send any additional data required to complete the request
putExtra(MY_EXTRA_DATASET_NAME, "my_dataset")
}
val intentSender: IntentSender = PendingIntent.getActivity(
this,
1001,
authIntent,
PendingIntent.FLAG_CANCEL_CURRENT
).intentSender
// Build a FillResponse object that requires authentication
val fillResponse: FillResponse = FillResponse.Builder()
.setAuthentication(autofillIds, intentSender, authPresentation)
.build()
Java
RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
authPresentation.setTextViewText(android.R.id.text1, "requires authentication");
Intent authIntent = new Intent(this, AuthActivity.class);
// Send any additional data required to complete the request
authIntent.putExtra(MY_EXTRA_DATASET_NAME, "my_dataset");
IntentSender intentSender = PendingIntent.getActivity(
this,
1001,
authIntent,
PendingIntent.FLAG_CANCEL_CURRENT
).getIntentSender();
// Build a FillResponse object that requires authentication
FillResponse fillResponse = new FillResponse.Builder()
.setAuthentication(autofillIds, intentSender, authPresentation)
.build();
بعد أن يكمل النشاط عملية المصادقة، يجب أن يستدعي طريقة
setResult()، مع تمرير قيمة RESULT_OK، وضبط
EXTRA_AUTHENTICATION_RESULT الإضافية على كائن FillResponse الذي
يتضمن مجموعة البيانات التي تمت تعبئتها. يوضّح الرمز التالي مثالاً على كيفية عرض النتيجة بعد اكتمال عملية المصادقة:
Kotlin
// The data sent by the service and the structure are included in the intent
val datasetName: String? = intent.getStringExtra(MY_EXTRA_DATASET_NAME)
val structure: AssistStructure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE)
val parsedStructure: ParsedStructure = parseStructure(structure)
val (username, password) = fetchUserData(parsedStructure)
// Build the presentation of the datasets
val usernamePresentation =
RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
setTextViewText(android.R.id.text1, "my_username")
}
val passwordPresentation =
RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
setTextViewText(android.R.id.text1, "Password for my_username")
}
// Add the dataset to the response
val fillResponse: FillResponse = FillResponse.Builder()
.addDataset(Dataset.Builder()
.setValue(
parsedStructure.usernameId,
AutofillValue.forText(username),
usernamePresentation
)
.setValue(
parsedStructure.passwordId,
AutofillValue.forText(password),
passwordPresentation
)
.build()
).build()
val replyIntent = Intent().apply {
// Send the data back to the service
putExtra(MY_EXTRA_DATASET_NAME, datasetName)
putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse)
}
setResult(Activity.RESULT_OK, replyIntent)
Java
Intent intent = getIntent();
// The data sent by the service and the structure are included in the intent
String datasetName = intent.getStringExtra(MY_EXTRA_DATASET_NAME);
AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
ParsedStructure parsedStructure = parseStructure(structure);
UserData userData = fetchUserData(parsedStructure);
// Build the presentation of the datasets
RemoteViews usernamePresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
usernamePresentation.setTextViewText(android.R.id.text1, "my_username");
RemoteViews passwordPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username");
// Add the dataset to the response
FillResponse fillResponse = new FillResponse.Builder()
.addDataset(new Dataset.Builder()
.setValue(parsedStructure.usernameId,
AutofillValue.forText(userData.username), usernamePresentation)
.setValue(parsedStructure.passwordId,
AutofillValue.forText(userData.password), passwordPresentation)
.build())
.build();
Intent replyIntent = new Intent();
// Send the data back to the service
replyIntent.putExtra(MY_EXTRA_DATASET_NAME, datasetName);
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse);
setResult(RESULT_OK, replyIntent);
في حال الحاجة إلى فتح قفل مجموعة بيانات بطاقة الائتمان، يمكن للخدمة عرض واجهة مستخدم تطلب رمز التحقق من البطاقة (CVC). يمكنك إخفاء البيانات حتى يتم فتح مجموعة البيانات من خلال تقديم بيانات نموذجية، مثل اسم البنك وآخر أربعة أرقام من رقم بطاقة الائتمان. يوضح المثال التالي كيفية طلب المصادقة لمجموعة بيانات وإخفاء البيانات حتى يقدم المستخدم رمز التحقق من البطاقة (CVC):
Kotlin
// Parse the structure and fetch payment data
val parsedStructure: ParsedStructure = parseStructure(structure)
val paymentData: Payment = fetchPaymentData(parsedStructure)
// Build the presentation that shows the bank and the last four digits of the
// credit card number, such as 'Bank-1234'
val maskedPresentation: String = "${paymentData.bank}-" +
paymentData.creditCardNumber.substring(paymentData.creditCardNumber.length - 4)
val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
setTextViewText(android.R.id.text1, maskedPresentation)
}
// Prepare an intent that displays the UI that asks for the CVC
val cvcIntent = Intent(this, CvcActivity::class.java)
val cvcIntentSender: IntentSender = PendingIntent.getActivity(
this,
1001,
cvcIntent,
PendingIntent.FLAG_CANCEL_CURRENT
).intentSender
// Build a FillResponse object that includes a Dataset that requires authentication
val fillResponse: FillResponse = FillResponse.Builder()
.addDataset(
Dataset.Builder()
// The values in the dataset are replaced by the actual
// data once the user provides the CVC
.setValue(parsedStructure.creditCardId, null, authPresentation)
.setValue(parsedStructure.expDateId, null, authPresentation)
.setAuthentication(cvcIntentSender)
.build()
).build()
Java
// Parse the structure and fetch payment data
ParsedStructure parsedStructure = parseStructure(structure);
Payment paymentData = fetchPaymentData(parsedStructure);
// Build the presentation that shows the bank and the last four digits of the
// credit card number, such as 'Bank-1234'
String maskedPresentation = paymentData.bank + "-" +
paymentData.creditCardNumber.subString(paymentData.creditCardNumber.length - 4);
RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
authPresentation.setTextViewText(android.R.id.text1, maskedPresentation);
// Prepare an intent that displays the UI that asks for the CVC
Intent cvcIntent = new Intent(this, CvcActivity.class);
IntentSender cvcIntentSender = PendingIntent.getActivity(
this,
1001,
cvcIntent,
PendingIntent.FLAG_CANCEL_CURRENT
).getIntentSender();
// Build a FillResponse object that includes a Dataset that requires authentication
FillResponse fillResponse = new FillResponse.Builder()
.addDataset(new Dataset.Builder()
// The values in the dataset are replaced by the actual
// data once the user provides the CVC
.setValue(parsedStructure.creditCardId, null, authPresentation)
.setValue(parsedStructure.expDateId, null, authPresentation)
.setAuthentication(cvcIntentSender)
.build())
.build();
بعد أن يتحقّق النشاط من رمز التحقّق من البطاقة (CVC)، يجب أن يستدعي طريقة setResult()،
مع تمرير قيمة RESULT_OK، وضبط القيمة الإضافية EXTRA_AUTHENTICATION_RESULT على
عنصر Dataset يحتوي على رقم بطاقة الائتمان وتاريخ انتهاء الصلاحية. تحل مجموعة البيانات الجديدة محل مجموعة البيانات التي تتطلب المصادقة، ويتم ملء طرق العرض على الفور. يوضّح الرمز التالي مثالاً على كيفية عرض مجموعة البيانات بعد أن يقدّم المستخدم رمز التحقق من البطاقة (CVC):
Kotlin
// Parse the structure and fetch payment data.
val parsedStructure: ParsedStructure = parseStructure(structure)
val paymentData: Payment = fetchPaymentData(parsedStructure)
// Build a non-null RemoteViews object to use as the presentation when
// creating the Dataset object. This presentation isn't actually used, but the
// Builder object requires a non-null presentation.
val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1)
// Create a dataset with the credit card number and expiration date.
val responseDataset: Dataset = Dataset.Builder()
.setValue(
parsedStructure.creditCardId,
AutofillValue.forText(paymentData.creditCardNumber),
notUsed
)
.setValue(
parsedStructure.expDateId,
AutofillValue.forText(paymentData.expirationDate),
notUsed
)
.build()
val replyIntent = Intent().apply {
putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset)
}
Java
// Parse the structure and fetch payment data.
ParsedStructure parsedStructure = parseStructure(structure);
Payment paymentData = fetchPaymentData(parsedStructure);
// Build a non-null RemoteViews object to use as the presentation when
// creating the Dataset object. This presentation isn't actually used, but the
// Builder object requires a non-null presentation.
RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
// Create a dataset with the credit card number and expiration date.
Dataset responseDataset = new Dataset.Builder()
.setValue(parsedStructure.creditCardId,
AutofillValue.forText(paymentData.creditCardNumber), notUsed)
.setValue(parsedStructure.expDateId,
AutofillValue.forText(paymentData.expirationDate), notUsed)
.build();
Intent replyIntent = new Intent();
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset);
تنظيم البيانات في مجموعات منطقية
يجب أن تنظّم خدمات الملء التلقائي البيانات في مجموعات منطقية تفصل المفاهيم من النطاقات المختلفة. في هذه الصفحة، يُشار إلى هذه المجموعات المنطقية باسم الأقسام. تعرض القائمة التالية أمثلة نموذجية للأقسام والحقول:
- بيانات الاعتماد، والتي تشمل حقلَي اسم المستخدم وكلمة المرور
- العنوان، والذي يتضمن حقول الشارع والمدينة والولاية والرمز البريدي.
- معلومات الدفع، والتي تتضمن رقم بطاقة الائتمان وتاريخ انتهاء الصلاحية وحقول رمز التحقق.
تستطيع خدمة الملء التلقائي التي تقسّم البيانات بشكل صحيح حماية بيانات مستخدميها بشكل أفضل من خلال عدم الكشف عن البيانات من أكثر من قسم واحد في مجموعة البيانات. على سبيل المثال، لا تحتاج مجموعة البيانات التي تتضمن بيانات الاعتماد إلى تضمين معلومات الدفع. يتيح تنظيم البيانات في أقسام لخدمتك الكشف عن الحد الأدنى من المعلومات ذات الصلة المطلوبة لتلبية الطلب.
يتيح تنظيم البيانات في أقسام للخدمات ملء الأنشطة التي تحتوي على طرق عرض من أقسام متعددة مع إرسال الحد الأدنى من البيانات ذات الصلة إلى تطبيق العميل. على سبيل المثال، لنفترض أنّ هناك نشاطًا يتضمن طرق عرض لاسم المستخدم وكلمة المرور والشارع والمدينة، وخدمة ملء تلقائي تحتوي على البيانات التالية:
| القسم | الحقل 1 | الحقل 2 |
|---|---|---|
| بيانات الاعتماد | work_username | work_password |
| personal_username | personal_password | |
| العنوان | work_street | work_city |
| personal_street | personal_city |
يمكن للخدمة إعداد مجموعة بيانات تتضمّن قسم بيانات الاعتماد لكل من حسابات العمل والحسابات الشخصية. عندما يختار المستخدم مجموعة بيانات، يمكن أن يوفّر ردّ الملء التلقائي اللاحق إما عنوان العمل أو العنوان الشخصي، وذلك حسب اختيار المستخدم الأول.
يمكن للخدمة تحديد الحقل الذي أنشأ الطلب من خلال استدعاء الإجراء isFocused() أثناء التنقّل في العنصر AssistStructure.
يتيح ذلك للخدمة إعداد FillResponse باستخدام بيانات القسم المناسبة.
الملء التلقائي للرموز التي يتم إرسالها عبر رسائل SMS لمرة واحدة
يمكن لخدمة الملء التلقائي مساعدة المستخدم في ملء الرموز التي تُستخدَم لمرة واحدة والمرسَلة باستخدام واجهة برمجة التطبيقات SMS Retriever API.
لاستخدام هذه الميزة، يجب استيفاء المتطلبات التالية:
- تعمل خدمة الملء التلقائي على الإصدار 9 من نظام التشغيل Android (المستوى 28 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث.
- يمنح المستخدم موافقته لخدمة الملء التلقائي على قراءة الرموز التي يتم استخدامها لمرة واحدة من الرسائل القصيرة SMS.
- التطبيق الذي توفّر له ميزة الملء التلقائي لا يستخدم حاليًا واجهة برمجة التطبيقات SMS Retriever API لقراءة الرموز التي يتم استخدامها لمرة واحدة.
يمكن لخدمة الملء التلقائي استخدام SmsCodeAutofillClient، وهي متاحة من خلال
طلب SmsCodeRetriever.getAutofillClient() من الإصدار 19.0.56 من "خدمات Google Play"
أو الإصدارات الأحدث.
في ما يلي الخطوات الأساسية لاستخدام واجهة برمجة التطبيقات هذه في خدمة الملء التلقائي:
- في خدمة الملء التلقائي، استخدِم
hasOngoingSmsRequestمنSmsCodeAutofillClientلتحديد ما إذا كانت هناك أي طلبات نشطة لاسم حزمة التطبيق الذي تملأه تلقائيًا. يجب ألا تعرض خدمة الملء التلقائي طلب اقتراح إلا إذا كانت هذه القيمةfalse. - في خدمة الملء التلقائي، استخدِم
checkPermissionStateمنSmsCodeAutofillClientللتحقّق مما إذا كانت خدمة الملء التلقائي لديها إذن لملء الرموز التي يتم استخدامها لمرة واحدة تلقائيًا. يمكن أن تكون حالة الإذن هذهNONEأوGRANTEDأوDENIED. يجب أن تعرض خدمة الملء التلقائي طلب اقتراح للحالتينNONEوGRANTED. - في نشاط مصادقة الملء التلقائي، استخدِم الإذن
SmsRetriever.SEND_PERMISSIONلتسجيلBroadcastReceiverيستمع إلىSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTIONلتلقّي نتيجة رمز SMS عند توفّره. يمكنك الاتصال بالرقم
startSmsCodeRetrieverعلىSmsCodeAutofillClientلبدء الاستماع إلى الرموز التي يتم إرسالها لمرة واحدة باستخدام رسائل SMS. إذا منح المستخدم أذونات لخدمة الملء التلقائي لاسترداد الرموز لمرة واحدة من الرسائل القصيرة، فسيتم البحث عن الرسائل القصيرة المستلمة في آخر دقيقة إلى خمس دقائق من الآن.إذا كانت خدمة الملء التلقائي تحتاج إلى طلب إذن المستخدم لقراءة الرموز التي تُستخدَم لمرة واحدة، قد يتعذّر تنفيذ
Taskالذي يعرضهstartSmsCodeRetrieverمع عرضResolvableApiException. في هذه الحالة، عليك استدعاء طريقةResolvableApiException.startResolutionForResult()لعرض مربّع إفادة الموافقة لطلب الحصول على الإذن.تلقّي نتيجة رمز SMS من الطلب، ثم عرض رمز SMS كردّ ملء تلقائي
تفعيل ميزة "الملء التلقائي" في Chrome
يسمح Chrome لخدمات الملء التلقائي التابعة لجهات خارجية بملء النماذج تلقائيًا، ما يمنح المستخدمين تجربة أكثر سلاسة وبساطة. لاستخدام خدمات الملء التلقائي التابعة لجهات خارجية من أجل ملء كلمات المرور ومفاتيح المرور والمعلومات الأخرى تلقائيًا، مثل العناوين وبيانات الدفع، على المستخدمين اختيار الملء التلقائي باستخدام خدمة أخرى في إعدادات Chrome.
لمساعدة المستخدمين في الحصول على أفضل تجربة ملء تلقائي ممكنة من خلال خدمتك وChrome على Android، على مقدّمي خدمات الملء التلقائي تشجيع المستخدمين على تحديد مقدّم الخدمة المفضّل لديهم في إعدادات Chrome.
لمساعدة المستخدمين في تفعيل زر التبديل، يمكن للمطوّرين اتّباع الخطوات التالية:
- الاستعلام عن إعدادات Chrome ومعرفة ما إذا كان المستخدم يريد استخدام خدمة ملء تلقائي تابعة لجهة خارجية
- رابط لصفحة إعدادات Chrome حيث يمكن للمستخدمين تفعيل خدمات الملء التلقائي التابعة لجهات خارجية
تحديد الحد الأقصى لإصدارات Chrome لوضع التوافق
توقّف Chrome عن إتاحة "وضع التوافق" بدءًا من الإصدار 137 لصالح ميزة "الملء التلقائي في Android". قد يؤدي الاحتفاظ بوضع التوافق إلى حدوث مشاكل في الثبات. حدِّد الحد الأقصى لإصدار حِزم Chrome التي تتيح وضع التوافق لضمان الثبات على النحو التالي.
<autofill-service>
...
<compatibility-package android:name="com.android.chrome" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.beta" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.dev" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.canary" android:maxLongVersionCode="711900039" />
...
</autofill-service>
قراءة إعدادات Chrome
يمكن لأي تطبيق معرفة ما إذا كان Chrome يستخدم وضع الملء التلقائي التابع لجهة خارجية والذي يسمح له باستخدام ميزة "الملء التلقائي" في Android. يستخدم Chrome واجهة برمجة التطبيقات ContentProvider في Android لإرسال
هذه المعلومات. يجب الإعلان في ملف بيان Android عن القنوات التي تريد قراءة الإعدادات منها:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<queries>
<!-- To Query Chrome Beta: -->
<package android:name="com.chrome.beta" />
<!-- To Query Chrome Stable: -->
<package android:name="com.android.chrome" />
</queries>
بعد ذلك، استخدِم ContentResolver في Android لطلب هذه المعلومات من خلال
إنشاء معرّف الموارد المنتظم للمحتوى:
Kotlin
val CHROME_CHANNEL_PACKAGE = "com.android.chrome" // Chrome Stable.
val CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider"
val THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state"
val THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode"
val uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME)
.path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
.build()
val cursor = contentResolver.query(
uri,
arrayOf(THIRD_PARTY_MODE_COLUMN), // projection
null, // selection
null, // selectionArgs
null // sortOrder
)
if (cursor == null) {
// Terminate now! Chromium versions older than this don't provide this information.
}
cursor?.use { // Use the safe call operator and the use function for auto-closing
if (it.moveToFirst()) { // Check if the cursor has any rows
val index = it.getColumnIndex(THIRD_PARTY_MODE_COLUMN)
if (index != -1) { // Check if the column exists
val value = it.getInt(index)
if (0 == value) {
// 0 means that the third party mode is turned off. Chrome uses its built-in
// password manager. This is the default for new users.
} else {
// 1 means that the third party mode is turned on. Chrome forwards all
// autofill requests to Android Autofill. Users have to opt-in for this.
}
} else {
// Handle the case where the column doesn't exist. Log a warning, perhaps.
Log.w("Autofill", "Column $THIRD_PARTY_MODE_COLUMN not found in cursor")
}
}
} // The cursor is automatically closed here
Java
final String CHROME_CHANNEL_PACKAGE = "com.android.chrome"; // Chrome Stable.
final String CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider";
final String THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state";
final String THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode";
final Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME)
.path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
.build();
final Cursor cursor = getContentResolver().query(
uri,
/*projection=*/new String[] {THIRD_PARTY_MODE_COLUMN},
/*selection=*/ null,
/*selectionArgs=*/ null,
/*sortOrder=*/ null);
if (cursor == null) {
// Terminate now! Chromium versions older than this don't provide this information.
}
cursor.moveToFirst(); // Retrieve the result;
int index = cursor.getColumnIndex(THIRD_PARTY_MODE_COLUMN);
if (0 == cursor.getInt(index)) {
// 0 means that the third party mode is turned off. Chrome uses its built-in
// password manager. This is the default for new users.
} else {
// 1 means that the third party mode is turned on. Chrome forwards all
// autofill requests to Android Autofill. Users have to opt-in for this.
}
رابط عميق لإعدادات Chrome
لإنشاء رابط لصفحة إعدادات Chrome حيث يمكن للمستخدمين تفعيل خدمات الملء التلقائي التابعة لجهات خارجية، استخدِم Intent على Android. تأكَّد من ضبط الإجراء والفئات كما هو موضّح في هذا المثال:
Kotlin
val autofillSettingsIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES)
autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT)
autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER)
autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE)
// Invoking the intent with a chooser allows users to select the channel they
// want to configure. If only one browser reacts to the intent, the chooser is
// skipped.
val chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel")
startActivity(chooser)
// If the caller knows which Chrome channel they want to configure,
// they can instead add a package hint to the intent, e.g.
val specificChromeIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES) // Create a *new* intent
specificChromeIntent.addCategory(Intent.CATEGORY_DEFAULT)
specificChromeIntent.addCategory(Intent.CATEGORY_APP_BROWSER)
specificChromeIntent.addCategory(Intent.CATEGORY_PREFERENCE)
specificChromeIntent.setPackage("com.android.chrome") // Set the package on the *new* intent
startActivity(specificChromeIntent) // Start the *new* intent
Java
Intent autofillSettingsIntent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES);
autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE);
// Invoking the intent with a chooser allows users to select the channel they
// want to configure. If only one browser reacts to the intent, the chooser is
// skipped.
Intent chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel");
startActivity(chooser);
// If the caller knows which Chrome channel they want to configure,
// they can instead add a package hint to the intent, e.g.
autofillSettingsIntent.setPackage("com.android.chrome");
startActivity(autofillSettingsIntent);
سيناريوهات الملء التلقائي المتقدّمة
استخدِم ميزة "الملء التلقائي" في الحالات التالية:
الدمج مع لوحة المفاتيح
بدءًا من الإصدار Android 11، يتيح النظام الأساسي للوحات المفاتيح ومحرّرات أسلوب الإدخال الأخرى (IME) عرض اقتراحات الملء التلقائي بشكل مضمّن، بدلاً من استخدام قائمة منسدلة. لمزيد من المعلومات حول كيفية توافق خدمة الملء التلقائي مع هذه الوظيفة، يُرجى الاطّلاع على مقالة دمج الملء التلقائي مع لوحات المفاتيح.
تقسيم مجموعات البيانات إلى صفحات
يمكن أن يتجاوز ردّ الملء التلقائي الكبير حجم المعاملة المسموح به لعنصر
Binder الذي يمثّل العنصر البعيد المطلوب لمعالجة
الطلب. لتجنُّب حدوث استثناء في نظام Android في هذه السيناريوهات، يمكنك إبقاء FillResponse صغيرًا من خلال عدم إضافة أكثر من 20 عنصر Dataset في المرة الواحدة. إذا كانت استجابتك تحتاج إلى المزيد من مجموعات البيانات، فيمكنك إضافة
مجموعة بيانات تتيح للمستخدمين معرفة أن هناك المزيد من المعلومات واسترداد
المجموعة التالية من مجموعات البيانات عند تحديدها. لمزيد من المعلومات، يُرجى الاطّلاع على
addDataset(Dataset).
حفظ البيانات المقسّمة على شاشات متعددة
غالبًا ما تقسم التطبيقات بيانات المستخدم عبر شاشات متعددة ضمن نفس النشاط، مثل إنشاء حسابات مستخدمين جديدة. على سبيل المثال، قد تطلب الشاشة الأولى اسم مستخدم، وشاشة ثانية لكلمة مرور. في هذه الحالات، يجب أن تنتظر خدمة الملء التلقائي حتى يُدخل المستخدم البيانات في جميع الحقول ذات الصلة قبل عرض واجهة مستخدم حفظ الملء التلقائي. اتّبِع الخطوات التالية للتعامل مع هذه السيناريوهات:
- في طلب الملء الأول، أضِف حزمة حالة العميل في الاستجابة التي تحتوي على معرّفات الملء التلقائي للحقول الجزئية الموجودة في الشاشة.
- في طلب الملء الثاني، يمكنك استرداد حزمة حالة العميل والحصول على
معرّفات الملء التلقائي التي تم ضبطها في الطلب السابق من حالة العميل، وإضافة
هذه المعرّفات وعلامة
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLEإلى عنصرSaveInfoالمستخدَم في الردّ الثاني. في طلب الحفظ، استخدِم عناصر
FillContextالمناسبة للحصول على قيمة كل حقل. هناك سياق تعبئة واحد لكل طلب تعبئة.لمزيد من المعلومات، يُرجى الاطّلاع على الحفظ عند تقسيم البيانات في شاشات متعددة.
توفير منطق الإعداد والإيقاف لكل طلب
في كل مرة يتم فيها إرسال طلب ملء تلقائي، يرتبط نظام Android بالخدمة ويستدعي الأسلوب onConnected(). بعد أن تعالج الخدمة الطلب، يستدعي نظام Android طريقة onDisconnected() ويلغي الربط بالخدمة. يمكنك تنفيذ onConnected() لتوفير رمز برمجي
يتم تشغيله قبل معالجة الطلب وonDisconnected() لتوفير رمز برمجي
يتم تشغيله بعد معالجة الطلب.
تخصيص واجهة مستخدم حفظ الملء التلقائي
يمكن لخدمات الملء التلقائي تخصيص واجهة مستخدم حفظ الملء التلقائي لمساعدة المستخدمين في تحديد
ما إذا كانوا يريدون السماح للخدمة بحفظ بياناتهم. يمكن أن توفّر الخدمات معلومات إضافية حول ما يتم حفظه إما من خلال نص أو من خلال عرض مخصّص. يمكن للخدمات أيضًا تغيير مظهر الزر الذي يلغي طلب الحفظ وتلقّي إشعار عندما ينقر المستخدم على هذا الزر.
لمزيد من المعلومات، يُرجى الاطّلاع على صفحة مرجع SaveInfo.
وضع التوافق
يسمح وضع التوافق لخدمات الملء التلقائي باستخدام البنية الافتراضية لتسهيل الاستخدام لأغراض الملء التلقائي. وهي مفيدة بشكل خاص لتوفير وظيفة الملء التلقائي في المتصفحات التي لا تنفّذ واجهات برمجة تطبيقات الملء التلقائي بشكل صريح.
لاختبار خدمة الملء التلقائي باستخدام "وضع التوافق"، أضِف صراحةً المتصفّح أو التطبيق الذي يتطلّب "وضع التوافق" إلى القائمة المسموح بها. يمكنك التحقّق من الحزم المُدرَجة في القائمة المسموح بها من خلال تنفيذ الأمر التالي:
$ adb shell settings get global autofill_compat_mode_allowed_packages
إذا لم تكن الحزمة التي تختبرها مدرَجة، يمكنك إضافتها من خلال تشغيل الأمر التالي، حيث يمثّل pkgX حزمة التطبيق:
$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]
إذا كان التطبيق متصفّحًا، استخدِم resIdx لتحديد رقم تعريف المورد لحقل الإدخال الذي يحتوي على عنوان URL للصفحة المعروضة.
يخضع وضع التوافق للقيود التالية:
- يتم إرسال طلب حفظ عندما تستخدم الخدمة العلامة
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLEأو عند استدعاء الطريقةsetTrigger(). يتم ضبطFLAG_SAVE_ON_ALL_VIEWS_INVISIBLEتلقائيًا عند استخدام وضع التوافق. - قد لا تتوفّر القيمة النصية للعُقد في طريقة
onSaveRequest(SaveRequest, SaveCallback).
لمزيد من المعلومات حول وضع التوافق، بما في ذلك القيود المرتبطة به، يُرجى الاطّلاع على مرجع فئة AutofillService.