خدمات تکمیل خودکار را بسازید

سرویس تکمیل خودکار برنامه‌ای است که با تزریق داده‌ها به نماهای برنامه‌های دیگر، پر کردن فرم‌ها را برای کاربران آسان‌تر می‌کند. سرویس‌های تکمیل خودکار همچنین می‌توانند داده‌های کاربر را از نماهای یک برنامه بازیابی کرده و برای استفاده در زمان بعدی ذخیره کنند. خدمات تکمیل خودکار معمولاً توسط برنامه هایی ارائه می شود که داده های کاربر را مدیریت می کنند، مانند مدیران رمز عبور.

Android با چارچوب تکمیل خودکار موجود در Android 8.0 (سطح API 26) و بالاتر، پر کردن فرم‌ها را آسان‌تر می‌کند. کاربران تنها در صورتی می توانند از ویژگی های تکمیل خودکار استفاده کنند که برنامه ای وجود داشته باشد که خدمات تکمیل خودکار را در دستگاه آنها ارائه دهد.

این صفحه نحوه پیاده سازی سرویس تکمیل خودکار را در برنامه خود نشان می دهد. اگر به دنبال نمونه کدی هستید که نحوه اجرای یک سرویس را نشان دهد، نمونه AutofillFramework را در جاوا یا کاتلین ببینید. برای جزئیات بیشتر در مورد نحوه عملکرد خدمات تکمیل خودکار، به صفحات مرجع برای کلاس های 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 از کاربر درخواست کند تنظیمات تکمیل خودکار را تغییر دهد. اگر کاربر سرویس تکمیل خودکار را انتخاب کند که با بسته تماس گیرنده مطابقت دارد، intent مقدار RESULT_OK را برمی گرداند.

نماهای مشتری را پر کنید

سرویس تکمیل خودکار درخواست‌هایی برای پر کردن نماهای مشتری در هنگام تعامل کاربر با برنامه‌های دیگر دریافت می‌کند. اگر سرویس تکمیل خودکار داده‌های کاربر را داشته باشد که درخواست را برآورده کند، داده‌های موجود در پاسخ را ارسال می‌کند. سیستم اندروید یک UI تکمیل خودکار را با داده های موجود نشان می دهد، همانطور که در شکل 1 نشان داده شده است:

تکمیل خودکار UI

شکل 1. UI تکمیل خودکار یک مجموعه داده را نمایش می دهد.

چارچوب تکمیل خودکار یک گردش کار را برای پر کردن نماها تعریف می کند که برای به حداقل رساندن زمان اتصال سیستم Android به سرویس تکمیل خودکار طراحی شده است. در هر درخواست، سیستم اندروید یک شی AssistStructure با فراخوانی متد onFillRequest() به سرویس ارسال می کند.

سرویس تکمیل خودکار بررسی می کند که آیا می تواند درخواست را با داده های کاربر که قبلاً ذخیره کرده است برآورده کند یا خیر. اگر بتواند درخواست را برآورده کند، سرویس داده ها را در اشیاء Dataset بسته بندی می کند. این سرویس متد onSuccess() را فراخوانی می‌کند و یک شی FillResponse ارسال می‌کند که حاوی اشیاء Dataset است. اگر سرویس داده ای برای ارضای درخواست نداشته باشد، به متد onSuccess() null می دهد.

اگر در پردازش درخواست خطایی وجود داشته باشد، سرویس متد onFailure() را فراخوانی می کند. برای توضیح دقیق گردش کار، به توضیحات در صفحه مرجع AutofillService مراجعه کنید.

کد زیر نمونه ای از متد onFillRequest() را نشان می دهد:

کاتلین

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)

جاوا

@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;
}

یک سرویس می تواند بیش از یک مجموعه داده داشته باشد که درخواست را برآورده کند. در این حالت، سیستم اندروید چندین گزینه را در رابط کاربری تکمیل خودکار نشان می‌دهد - یکی برای هر مجموعه داده. مثال کد زیر نحوه ارائه مجموعه داده های متعدد در یک پاسخ را نشان می دهد:

کاتلین

// 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()

جاوا

// 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() بازیابی کند.

یک سرویس باید بتواند محتوای یک view را توصیف کند تا بررسی کند که آیا می تواند درخواست را برآورده کند یا خیر. استفاده از ویژگی autofillHints اولین رویکردی است که یک سرویس باید برای توصیف محتویات یک view استفاده کند. با این حال، برنامه های سرویس گیرنده باید به صراحت این ویژگی را در نماهای خود قبل از اینکه در دسترس سرویس قرار گیرند، ارائه دهند.

اگر یک برنامه مشتری ویژگی autofillHints را ارائه نکند، یک سرویس باید از اکتشافات خود برای توصیف محتوا استفاده کند. این سرویس می‌تواند از روش‌های کلاس‌های دیگر، مانند getText() یا getHint() برای دریافت اطلاعات در مورد محتوای view استفاده کند. برای اطلاعات بیشتر، به ارائه نکات برای تکمیل خودکار مراجعه کنید.

مثال زیر نحوه عبور از AssistStructure و بازیابی داده های تکمیل خودکار از یک شی ViewNode را نشان می دهد:

کاتلین

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

جاوا

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 نشان داده شده است.

تکمیل خودکار ذخیره UI

شکل 2. تکمیل خودکار ذخیره UI.

برای ذخیره داده ها، سرویس باید نشان دهد که علاقه مند به ذخیره داده ها برای استفاده در آینده است. قبل از اینکه سیستم اندروید درخواستی برای ذخیره داده ارسال کند، یک درخواست پر وجود دارد که در آن سرویس این فرصت را دارد که نماها را پر کند. برای نشان دادن علاقه مندی به ذخیره داده ها، این سرویس شامل یک شی SaveInfo در پاسخ به درخواست پر می شود. شی SaveInfo حداقل حاوی داده های زیر است:

  • نوع داده های کاربر که ذخیره می شود. برای فهرستی از مقادیر SAVE_DATA موجود، SaveInfo ببینید.
  • حداقل مجموعه نماهایی که برای راه اندازی درخواست ذخیره باید تغییر کنند. به عنوان مثال، یک فرم ورود به سیستم معمولاً از کاربر می‌خواهد که نمای username و password را برای راه‌اندازی درخواست ذخیره به‌روزرسانی کند.

یک شی SaveInfo با یک شی FillResponse مرتبط است، همانطور که در مثال کد زیر نشان داده شده است:

کاتلین

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()
    ...
}

جاوا

@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() را نشان می دهد:

کاتلین

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()
}

جاوا

@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();
}

سرویس‌های تکمیل خودکار باید داده‌های حساس را قبل از تداوم آن رمزگذاری کنند. با این حال، داده‌های کاربر می‌تواند شامل برچسب‌ها یا داده‌هایی باشد که حساس نیستند. به عنوان مثال، یک حساب کاربری می تواند دارای برچسبی باشد که داده ها را به عنوان یک حساب کاری یا شخصی علامت گذاری می کند. سرویس ها نباید برچسب ها را رمزگذاری کنند. با رمزگذاری نکردن برچسب‌ها، اگر کاربر احراز هویت نکرده باشد، سرویس‌ها می‌توانند از برچسب‌ها در نماهای ارائه استفاده کنند. سپس، سرویس ها می توانند برچسب ها را با داده های واقعی پس از احراز هویت کاربر جایگزین کنند.

UI ذخیره تکمیل خودکار را به تعویق بیندازید

با شروع Android 10، اگر از چندین صفحه برای پیاده‌سازی یک گردش کار تکمیل خودکار استفاده می‌کنید - به عنوان مثال، یک صفحه برای قسمت نام کاربری و دیگری برای رمز عبور - می‌توانید با استفاده از پرچم SaveInfo.FLAG_DELAY_SAVE ، رابط کاربری ذخیره‌سازی تکمیل خودکار را به تعویق بیندازید.

اگر این پرچم تنظیم شود، هنگامی که زمینه تکمیل خودکار مرتبط با پاسخ SaveInfo متعهد شود، رابط کاربری ذخیره خودکار تکمیل نمی شود. در عوض، می‌توانید از یک فعالیت جداگانه در همان کار برای ارائه درخواست‌های تکمیل آینده استفاده کنید و سپس UI را از طریق یک درخواست ذخیره نشان دهید. برای اطلاعات بیشتر، SaveInfo.FLAG_DELAY_SAVE ببینید.

نیاز به احراز هویت کاربر

سرویس‌های تکمیل خودکار می‌توانند سطح امنیتی بیشتری را با الزام کاربر به احراز هویت قبل از پر کردن نماها فراهم کنند. سناریوهای زیر کاندیدهای خوبی برای اجرای احراز هویت کاربر هستند:

  • قفل اطلاعات کاربر در برنامه باید با استفاده از رمز عبور اصلی یا اسکن اثر انگشت باز شود.
  • یک مجموعه داده خاص باید باز شود، مانند جزئیات کارت اعتباری با استفاده از کد تأیید کارت (CVC).

در سناریویی که سرویس قبل از باز کردن قفل داده ها نیاز به احراز هویت کاربر دارد، سرویس می تواند داده های دیگ بخار یا برچسبی را ارائه کند و Intent را که از احراز هویت مراقبت می کند را مشخص کند. اگر پس از انجام جریان احراز هویت برای پردازش درخواست به داده های اضافی نیاز دارید، می توانید چنین داده هایی را به intent اضافه کنید. سپس فعالیت احراز هویت شما می تواند داده ها را به کلاس AutofillService در برنامه شما برگرداند.

مثال کد زیر نحوه تعیین اینکه درخواست نیاز به احراز هویت دارد را نشان می دهد:

کاتلین

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

جاوا

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 که شامل مجموعه داده پر شده است تنظیم کند. کد زیر نمونه ای از نحوه برگرداندن نتیجه را پس از تکمیل جریان احراز هویت نشان می دهد:

کاتلین

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

جاوا

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

در سناریویی که یک مجموعه داده کارت اعتباری باید باز شود، سرویس می‌تواند یک UI را نشان دهد که CVC را درخواست می‌کند. می‌توانید با ارائه داده‌های دیگ بخار، مانند نام بانک و چهار رقم آخر شماره کارت اعتباری، داده‌ها را پنهان کنید تا زمانی که مجموعه داده باز شود. مثال زیر نشان می دهد که چگونه می توان برای یک مجموعه داده احراز هویت نیاز داشت و داده ها را پنهان کرد تا زمانی که کاربر CVC را ارائه کند:

کاتلین

// 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()

جاوا

// 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 توسط کاربر نشان می دهد:

کاتلین

// 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)
}

جاوا

// 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_password
نام کاربری_personal personal_password
آدرس خیابان_کار کار_شهر
خیابان_شخصی شهر شخصی

این سرویس می تواند مجموعه داده ای را آماده کند که شامل پارتیشن اعتبارنامه برای حساب های کاری و شخصی باشد. هنگامی که کاربر یک مجموعه داده را انتخاب می کند، بسته به انتخاب اول کاربر، پاسخ تکمیل خودکار بعدی می تواند آدرس کاری یا شخصی را ارائه دهد.

یک سرویس می‌تواند با فراخوانی متد isFocused() در حین عبور از شی AssistStructure ، فیلدی را که درخواست را آغاز کرده است شناسایی کند. این به سرویس اجازه می دهد تا یک FillResponse با داده های پارتیشن مناسب آماده کند.

تکمیل خودکار کد یکبار مصرف پیامک

سرویس تکمیل خودکار شما می تواند با استفاده از SMS Retriever API به کاربر در پر کردن کدهای یکبار مصرف ارسال شده از طریق پیامک کمک کند.

برای استفاده از این ویژگی، شرایط زیر باید رعایت شود:

  • سرویس تکمیل خودکار روی Android 9 (سطح API 28) یا بالاتر اجرا می‌شود.
  • کاربر برای سرویس تکمیل خودکار شما برای خواندن کدهای یکبار مصرف از پیامک رضایت می دهد.
  • برنامه‌ای که تکمیل خودکار آن را ارائه می‌دهید از قبل از SMS Retriever API برای خواندن کدهای یکبار مصرف استفاده نمی‌کند.

سرویس تکمیل خودکار شما می‌تواند از SmsCodeAutofillClient استفاده کند که با تماس با SmsCodeRetriever.getAutofillClient() از Google Play Services 19.0.56 یا بالاتر در دسترس است.

مراحل اولیه برای استفاده از این API در یک سرویس تکمیل خودکار عبارتند از:

  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، این پلتفرم به صفحه‌کلیدها و سایر ویرایشگرهای روش ورودی ( IME ) اجازه می‌دهد به‌جای استفاده از منوی کشویی، پیشنهادات تکمیل خودکار را به‌صورت درون خطی نمایش دهند. برای اطلاعات بیشتر در مورد اینکه چگونه سرویس تکمیل خودکار شما می تواند از این عملکرد پشتیبانی کند، به ادغام تکمیل خودکار با صفحه کلید مراجعه کنید.
صفحه بندی مجموعه داده ها
یک پاسخ تکمیل خودکار بزرگ می‌تواند از اندازه تراکنش مجاز شی Binder که نشان‌دهنده شی قابل راه‌حل مورد نیاز برای پردازش درخواست است، بیشتر شود. برای جلوگیری از ایجاد استثنا توسط سیستم اندروید در این سناریوها، می توانید FillResponse را با اضافه کردن بیش از 20 شیء Dataset در یک زمان کوچک نگه دارید. اگر پاسخ شما به مجموعه داده‌های بیشتری نیاز دارد، می‌توانید مجموعه داده‌ای اضافه کنید که به کاربران اطلاع دهد اطلاعات بیشتری وجود دارد و در صورت انتخاب، گروه بعدی مجموعه داده‌ها را بازیابی می‌کند. برای اطلاعات بیشتر، addDataset(Dataset) ببینید.
تقسیم داده ها را در چند صفحه ذخیره کنید

برنامه ها اغلب در یک فعالیت، داده های کاربر را در چندین صفحه تقسیم می کنند، به خصوص در فعالیت هایی که برای ایجاد یک حساب کاربری جدید استفاده می شود. به عنوان مثال، صفحه اول یک نام کاربری می خواهد و اگر نام کاربری موجود باشد، صفحه دوم یک رمز عبور می خواهد. در این مواقع، سرویس تکمیل خودکار باید منتظر بماند تا کاربر هر دو فیلد را وارد کند تا رابط کاربری ذخیره خودکار تکمیل شود. برای مدیریت چنین سناریوهایی مراحل زیر را دنبال کنید:

  1. در اولین درخواست پر کردن ، یک بسته حالت کلاینت را در پاسخ اضافه کنید که حاوی شناسه‌های تکمیل خودکار فیلدهای جزئی موجود در صفحه است.
  2. در درخواست تکمیل دوم، بسته حالت کلاینت را بازیابی کنید، شناسه‌های تکمیل خودکار تنظیم شده در درخواست قبلی را از حالت مشتری دریافت کنید و این شناسه‌ها و پرچم FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE را به شی SaveInfo استفاده شده در پاسخ دوم اضافه کنید.
  3. در درخواست ذخیره ، از اشیاء FillContext مناسب برای بدست آوردن مقدار هر فیلد استفاده کنید. برای هر درخواست پر کردن یک زمینه پر کردن وجود دارد.

برای اطلاعات بیشتر، به ذخیره زمانی که داده ها در چندین صفحه تقسیم می شوند، مراجعه کنید.

برای هر درخواست، منطق اولیه و حذف را ارائه دهید

هر بار که یک درخواست تکمیل خودکار وجود دارد، سیستم Android به سرویس متصل می شود و متد onConnected() آن را فراخوانی می کند. هنگامی که سرویس درخواست را پردازش کرد، سیستم اندروید متد onDisconnected() را فراخوانی می کند و از سرویس جدا می شود. شما می توانید onConnected() برای ارائه کدی که قبل از پردازش درخواست اجرا می شود و onDisconnected() برای ارائه کدی که پس از پردازش درخواست اجرا می شود، پیاده سازی کنید.

UI ذخیره تکمیل خودکار را سفارشی کنید

سرویس‌های تکمیل خودکار می‌توانند رابط کاربری ذخیره‌سازی تکمیل خودکار را سفارشی کنند تا به کاربران کمک کنند تصمیم بگیرند که آیا می‌خواهند به سرویس اجازه دهند داده‌هایشان را ذخیره کند یا خیر. سرویس‌ها می‌توانند اطلاعات بیشتری در مورد آنچه ذخیره می‌شوند از طریق یک متن ساده یا از طریق یک نمای سفارشی شده ارائه دهند. سرویس‌ها همچنین می‌توانند ظاهر دکمه‌ای را که درخواست ذخیره را لغو می‌کند تغییر دهند و هنگامی که کاربر روی آن دکمه ضربه می‌زند، اعلان دریافت کنند. برای اطلاعات بیشتر، به صفحه مرجع SaveInfo مراجعه کنید.

حالت سازگاری

حالت سازگاری به سرویس‌های تکمیل خودکار اجازه می‌دهد از ساختار مجازی دسترسی برای اهداف تکمیل خودکار استفاده کنند. این به ویژه برای ارائه قابلیت تکمیل خودکار در مرورگرهایی که به صراحت APIهای تکمیل خودکار را پیاده سازی نمی کنند مفید است.

برای آزمایش سرویس تکمیل خودکار خود با استفاده از حالت سازگاری، مرورگر یا برنامه‌ای را که به حالت سازگاری نیاز دارد به صراحت فهرست کنید. با اجرای دستور زیر می توانید بررسی کنید که کدام بسته ها قبلاً در لیست مجاز هستند:

$ 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 مراجعه کنید.