إنشاء خدمات الملء التلقائي

خدمة الملء التلقائي هي تطبيق يسهّل على المستخدمين ملء النماذج عن طريق إدخال البيانات في طرق عرض التطبيقات الأخرى. يمكن لخدمات الملء التلقائي أيضًا استرداد بيانات المستخدم من طرق العرض في أحد التطبيقات وتخزينها لاستخدامها في وقت لاحق. عادةً ما يتم توفير خدمات الملء التلقائي من خلال التطبيقات التي تدير بيانات المستخدمين، مثل تطبيقات إدارة كلمات المرور.

يجعل Android عملية ملء النماذج أسهل باستخدام إطار عمل الملء التلقائي المتاح في Android 8.0 (المستوى 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:

واجهة المستخدم للملء التلقائي

الشكل 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
بيانات الاعتماد اسم مستخدم_العمل كلمة مرور_العمل
اسم المستخدم الشخصي الشخصية_password
العنوان شارع_العمل مدينة_العمل
الشارع_الشخصي المدينة_الشخصية

يمكن للخدمة إعداد مجموعة بيانات تتضمن قسم بيانات الاعتماد لكل من حسابات العمل والحسابات الشخصية. عندما يختار المستخدم مجموعة بيانات، يمكن أن توفر استجابة الملء التلقائي اللاحقة إما عنوان العمل أو العنوان الشخصي، اعتمادًا على الخيار الأول للمستخدم.

يمكن للخدمة تحديد الحقل الذي أنشأ الطلب من خلال استدعاء الطريقة isFocused() أثناء اجتياز الكائن AssistStructure. يتيح ذلك للخدمة إعداد FillResponse باستخدام بيانات القسم المناسبة.

الملء التلقائي للرمز لمرة واحدة عبر الرسائل القصيرة

يمكن لخدمة الملء التلقائي مساعدة المستخدم في ملء الرموز التي تُستخدم لمرة واحدة والتي يتم إرسالها عبر الرسائل القصيرة SMS باستخدام واجهة برمجة التطبيقات SMS Retriever API.

لاستخدام هذه الميزة، يجب استيفاء المتطلبات التالية:

  • تعمل خدمة الملء التلقائي على نظام التشغيل Android 9 (المستوى 28 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث.
  • يمنح المستخدم موافقته على خدمة الملء التلقائي لقراءة الرموز لمرة واحدة من الرسائل القصيرة.
  • التطبيق الذي توفر ميزة الملء التلقائي له لا يستخدم بالفعل واجهة برمجة تطبيقات SMS Retriever API لقراءة الرموز لمرة واحدة.

يمكن لخدمة الملء التلقائي استخدام SmsCodeAutofillClient، المتاحة من خلال الاتصال بـ SmsCodeRetriever.getAutofillClient() من خدمات Google Play 19.0.56 أو الإصدارات الأحدث.

في ما يلي الخطوات الأساسية لاستخدام واجهة برمجة التطبيقات هذه في خدمة الملء التلقائي:

  1. في خدمة الملء التلقائي، استخدِم hasOngoingSmsRequest من SmsCodeAutofillClient لتحديد ما إذا كانت هناك أي طلبات نشطة لاسم حزمة التطبيق الذي تملأه تلقائيًا. يجب أن تعرض خدمة الملء التلقائي طلب اقتراح فقط إذا كان ذلك يعرض false.
  2. في خدمة الملء التلقائي، يمكنك استخدام checkPermissionState من SmsCodeAutofillClient للتحقّق مما إذا كانت خدمة الملء التلقائي لديها الإذن بالملء التلقائي للرموز لمرة واحدة. يمكن أن تكون حالة الإذن NONE أو GRANTED أو DENIED. يجب أن تعرض خدمة الملء التلقائي طلب اقتراح لحالتَي NONE وGRANTED.
  3. عند استخدام نشاط مصادقة الملء التلقائي، يمكنك استخدام الإذن SmsRetriever.SEND_PERMISSION لتسجيل صوت BroadcastReceiver في SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION لتلقّي نتيجة رمز الرسائل القصيرة SMS عند توفُّره.
  4. اتصل بالرقم startSmsCodeRetriever على SmsCodeAutofillClient لبدء الاستماع إلى الرموز التي تُستخدم مرة واحدة والتي يتم إرسالها عبر الرسائل القصيرة SMS. إذا منح المستخدم أذونات لخدمة الملء التلقائي لاسترداد رموز لمرة واحدة من الرسائل القصيرة، سيبحث هذا عن الرسائل القصيرة التي تم استلامها في آخر دقيقة إلى خمس دقائق من الآن.

    إذا كانت خدمة الملء التلقائي بحاجة إلى طلب إذن المستخدم لقراءة الرموز التي تُستخدم لمرة واحدة، قد يتعذّر عرض Task التي تم إرجاعها بحلول startSmsCodeRetriever من خلال عرض ResolvableApiException. إذا حدث ذلك، عليك استدعاء طريقة ResolvableApiException.startResolutionForResult() لعرض مربّع حوار الموافقة على طلب الإذن.

  5. استلِم نتيجة رمز الرسالة القصيرة من الغرض ثم اعرض رمز رسالة SMS كاستجابة ملء تلقائي.

سيناريوهات الملء التلقائي المتقدمة

الدمج مع لوحة المفاتيح
بدءًا من نظام التشغيل Android 11، يسمح النظام الأساسي للوحات المفاتيح وأدوات تحرير أساليب الإدخال الأخرى (IMEs) بعرض اقتراحات الملء التلقائي بشكل مضمَّن، بدلاً من استخدام القائمة المنسدلة. لمزيد من المعلومات حول كيفية إتاحة خدمة الملء التلقائي لهذه الوظيفة، يُرجى الاطّلاع على المقالة دمج ميزة الملء التلقائي مع لوحات المفاتيح.
تقسيم مجموعات البيانات على صفحات
يمكن أن تتجاوز استجابة الملء التلقائي الكبيرة حجم المعاملة المسموح به لعنصر Binder الذي يمثّل العنصر القابل عن بُعد المطلوب لمعالجة الطلب. لمنع نظام Android من طرح استثناء في هذه السيناريوهات، يمكنك إبقاء FillResponse صغيرًا من خلال إضافة ما لا يزيد عن 20 عنصر Dataset في المرة الواحدة. إذا احتاجت إجابتك إلى مزيد من مجموعات البيانات، فيمكنك إضافة مجموعة بيانات تتيح للمستخدمين معرفة أن هناك المزيد من المعلومات وتسترد المجموعة التالية من مجموعات البيانات عند تحديدها. لمزيد من المعلومات، يُرجى الاطّلاع على addDataset(Dataset).
حفظ تقسيم البيانات على شاشات متعددة

غالبًا ما تقسم التطبيقات بيانات المستخدم على شاشات متعددة في نفس النشاط، خاصة في الأنشطة المستخدمة لإنشاء حساب مستخدم جديد. على سبيل المثال، تطلب الشاشة الأولى اسم المستخدم، وإذا كان اسم المستخدم متاحًا، فستظهر شاشة ثانية تطلب إدخال كلمة المرور. في هذه الحالات، يجب أن تنتظر خدمة الملء التلقائي حتى يُدخِل المستخدم كلا الحقلَين قبل أن يتم عرض واجهة المستخدم للحفظ الخاصة بالملء التلقائي. اتبع هذه الخطوات للتعامل مع هذه السيناريوهات:

  1. في طلب التعبئة الأول، أضِف حزمة حالة العميل في الاستجابة التي تحتوي على معرّفات الملء التلقائي للحقول الجزئية الموجودة في الشاشة.
  2. في طلب التعبئة الثاني، استرِد حزمة حالة العميل، واحصل على أرقام تعريف الملء التلقائي التي تم ضبطها في الطلب السابق من حالة العميل، وأضِف هذه المعرّفات والعلامة FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE إلى عنصر SaveInfo المستخدَم في الاستجابة الثانية.
  3. في طلب الحفظ، استخدِم كائنات 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.