خدمة الملء التلقائي هي تطبيق يسهّل على المستخدمين ملء النماذج من خلال إدخال البيانات في طرق عرض التطبيقات الأخرى. يمكن لخدمات "الملء التلقائي" أيضًا استرداد بيانات المستخدم من طرق العرض في أحد التطبيقات وتخزينها لاستخدامها في وقت لاحق. عادةً ما تقدّم التطبيقات التي تدير بيانات المستخدمين، مثل تطبيقات إدارة كلمات المرور، خدمات الملء التلقائي.
يسهّل نظام التشغيل 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
في المثال السابق نشاطًا يتيح للمستخدمين ضبط الخدمة. يوضّح المثال التالي مصدر service_configuration
بتنسيق XML:
<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:
الشكل 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.
الشكل 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. إذا منح المستخدم أذونات لخدمة الملء التلقائي باسترداد الرموز لمرة واحدة من الرسائل القصيرة SMS، ستبحث الخدمة عن الرسائل القصيرة SMS التي تم تلقّيها خلال آخر دقيقة إلى خمس دقائق من الآن.إذا كانت خدمة الملء التلقائي بحاجة إلى طلب إذن المستخدم لقراءة الرموز الصالحة لمرة واحدة، قد يتعذّر عرض
Task
الذي تعرضه الدالةstartSmsCodeRetriever
مع عرض الرمزResolvableApiException
. في حال حدوث ذلك، عليك استدعاء الطريقةResolvableApiException.startResolutionForResult()
لعرض مربّع حوار الموافقة لطلب الإذن.تلقّي نتيجة رمز SMS من الغرض ثم إرجاع رمز SMS كاستجابة للتعبئة التلقائية
تفعيل ميزة "الملء التلقائي" في Chrome
يسمح Chrome لخدمات الملء التلقائي التابعة لجهات خارجية بملء النماذج تلقائيًا، ما يوفّر للمستخدمين تجربة أكثر سلاسة وبساطة. لاستخدام خدمات الملء التلقائي التابعة لجهات خارجية لملء كلمات المرور ومفاتيح المرور وغيرها من المعلومات، مثل العناوين وبيانات الدفع، على المستخدمين اختيار الملء التلقائي باستخدام خدمة أخرى في إعدادات Chrome.
لضمان حصول المستخدمين على أفضل تجربة ملء تلقائي ممكنة من خلال خدمتك ومتصفّح Chrome على Android، على مقدّمي خدمات الملء التلقائي تشجيع المستخدمين على تحديد مقدّم الخدمة المفضّل لديهم في إعدادات Chrome.
لمساعدة المستخدمين في تفعيل زر التبديل، يمكن للمطوّرين اتّخاذ الإجراءات التالية:
- الاستعلام عن إعدادات Chrome ومعرفة ما إذا كان المستخدم يريد استخدام خدمة خارجية للملء التلقائي
- رابط لصفحة إعدادات Chrome حيث يمكن للمستخدمين تفعيل خدمات الملء التلقائي التابعة لجهات خارجية
قراءة إعدادات 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 لطلب هذه المعلومات من خلال إنشاء معرّف الموارد المنتظم (URI) للمحتوى:
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! Older versions of Chromium 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 uses 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! Older versions of Chromium 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 uses 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(autofillSettingsInstent);
سيناريوهات الملء التلقائي المتقدّمة
- التكامل مع لوحة المفاتيح
- بدءًا من نظام التشغيل 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
.