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

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

يسهّل Android ملء النماذج باستخدام إطار عمل الملء التلقائي المتاح في Android 8.0 (المستوى 26 من واجهة برمجة التطبيقات) والإصدارات الأحدث. لا يمكن للمستخدمين الاستفادة من ميزات الملء التلقائي إلا إذا كان هناك تطبيق يقدّم خدمات الملء التلقائي على أجهزتهم.

توضِّح هذه الصفحة كيفية تنفيذ خدمة الملء التلقائي في تطبيقك. إذا كنت تبحث عن نموذج رمز برمجي يوضّح كيفية تنفيذ خدمة، يمكنك الاطّلاع على نموذج AutofillFramework في Java أو Kotlin. لمزيد من التفاصيل حول آلية عمل خدمات الملء التلقائي، يُرجى الاطّلاع على صفحات مرجع للفئتَين AutofillService وAutofillManager.

تعريفات البيان والأذونات

يجب أن تتضمّن التطبيقات التي توفّر خدمات الملء التلقائي بيانًا يصف طريقة تنفيذ الخدمة. لتحديد البيان، أدرِج عنصر <service> في بيان التطبيق. يجب أن يحتوي العنصر <service> على السمات والعناصر التالية:

  • سمة android:name التي تشير إلى الفئة الفرعية من AutofillService في التطبيق الذي ينفذ الخدمة
  • سمة android:permission التي تحدّد الإذن BIND_AUTOFILL_SERVICE
  • عنصر <intent-filter> الذي يحدّد الإجراء android.service.autofill.AutofillService العنصر الفرعي الإلزامي <action>
  • عنصر <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 intent. يعرض الإجراء قيمة 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 لاسترداد بيانات الملء التلقائي المطلوبة لتنفيذ الطلب. يمكن للخدمة استرداد بيانات الملء التلقائي باستخدام methods من فئة ViewNode ، مثل getAutofillId().

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

إذا لم يقدِّم تطبيق العميل السمة autofillHints ، يجب أن تستخدِم الخدمة أساليب البحث الاستقرائي الخاصة بها لوصف المحتوى. يمكن للخدمة استخدام طرق من فئات أخرى، مثل getText() أو getHint()، للحصول على معلومات عن محتوى العرض. لمزيد من المعلومات، يُرجى الاطّلاع على تقديم نصائح بشأن الملء التلقائي.

يوضّح المثال التالي كيفية التنقّل في AssistStructure واسترداد data الملء التلقائي من عنصر 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.

واجهة المستخدم لحفظ ميزة &quot;الملء التلقائي&quot;

الشكل 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). يمكنك إخفاء البيانات إلى أن تتم إلغاء قفل مجموعة البيانات من خلال تقديم بيانات نموذجية، مثل اسم المصرف والأرقام الأربعة الأخيرة من رقم بطاقة الائتمان. يوضّح المثال التالي كيفية طلب المصادقة لمجموعة بيانات وإخفاء البيانات إلى أن يقدّم المستخدم رقم التحقق من البطاقة:

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 يحتوي على رقم بطاقة الائتمان وتاريخ انتهاء صلاحيتها. تستبدل مجموعة البيانات الجديدة مجموعة البيانات التي تتطلّب المصادقة، ويتم ملء الملفات الشخصية على الفور. يعرض الرمز البرمجي التالي مثالاً على كيفية عرض مجموعة البيانات بعد أن يقدّم المستخدم رقم التحقق من البطاقة:

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);

تنظيم البيانات في مجموعات منطقية

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

  • بيانات الاعتماد، التي تتضمّن حقلَي اسم المستخدم وكلمة المرور
  • العنوان، الذي يتضمّن حقول الشارع والمدينة والولاية والرمز البريدي
  • معلومات الدفع، التي تتضمّن رقم بطاقة الائتمان وتاريخ انتهاء الصلاحية وحقل رمز التحقّق

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

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

قسم الحقل 1 الحقل 2
بيانات الاعتماد work_username work_password
personal_username personal_password
العنوان work_street work_city
personal_street personal_city

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

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

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

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

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

  • تعمل خدمة الملء التلقائي على الإصدار 9 من نظام التشغيل Android (المستوى 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 لتلقّي نتيجة رمز الرسائل القصيرة عند توفّره.
  4. يُرجى الاتصال على startSmsCodeRetriever في SmsCodeAutofillClient لبدء الاستماع إلى الرموز المُستخدَمة لمرة واحدة والتي يتم إرسالها عبر الرسائل القصيرة. إذا منح المستخدم أذونات لخدمة الملء التلقائي لاسترداد الرموز التي يتم إرسالها لمرة واحدة عبر الرسائل القصيرة، سيبحث هذا الإجراء عن الرسائل القصيرة التي تم استلامها في آخر دقيقتين إلى خمس دقائق من الآن.

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

  5. استلم نتيجة رمز الرسالة القصيرة من الطلب، ثم أعِد الرمز الوارد في الرسالة القصيرة كاستجابة للملء التلقائي.

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

الدمج مع لوحة المفاتيح
بدءًا من الإصدار 11 من Android، تسمح المنصة للوحات المفاتيح وأدوات التعديل الأخرى لطريقة الإدخال (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.