יצירת שירותי מילוי אוטומטי

שירות מילוי אוטומטי הוא אפליקציה שמאפשרת למשתמשים למלא טפסים בקלות על ידי החדרת נתונים לתצוגות של אפליקציות אחרות. אפשר גם להשתמש בשירותי המילוי האוטומטי לאחזר נתוני משתמש מהתצוגות באפליקציה ולאחסן אותם לשימוש במועד מאוחר יותר בזמן האימון. שירותי מילוי אוטומטי ניתנים בדרך כלל על ידי אפליקציות שמנהלות את נתוני המשתמשים, כמו מנהלי סיסמאות.

עם Android, קל יותר למלא טפסים בעזרת המסגרת למילוי אוטומטי Android מגרסה 8.0 (רמת API 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() ה-method של המחלקה AutofillManager.

אם האפליקציה היא לא שירות המילוי האוטומטי הנוכחי, היא יכולה לבקש מהמשתמש לשנות את הגדרות המילוי האוטומטי באמצעות הכוונה ACTION_REQUEST_SET_AUTOFILL_SERVICE. הכוונה מחזירה את הערך RESULT_OK אם המשתמש בחר שירות מילוי אוטומטי שתואם לחבילה של מבצע הקריאה החוזרת.

מילוי תצוגות של לקוחות

שירות המילוי האוטומטי מקבל בקשות למלא תצוגות של לקוחות כשהמשתמשים מקיימים אינטראקציה עם אפליקציות אחרות. אם שירות המילוי האוטומטי כולל נתוני משתמש שעונים על הדרישות את הבקשה, היא שולחת את הנתונים בתשובה. מערכת Android מציגה ממשק משתמש של מילוי אוטומטי עם הנתונים הזמינים, כפי שמוצג באיור 1:

ממשק המשתמש למילוי אוטומטי

איור 1. ממשק משתמש של מילוי אוטומטי שמציג מערך נתונים.

המסגרת של המילוי האוטומטי מגדירה תהליך עבודה למילוי תצוגות, שנועד לצמצם את משך הזמן שבו מערכת Android מקושרת לשירות המילוי האוטומטי. בכל בקשה, מערכת Android שולחת לאובייקט AssistStructure לשירות באמצעות קריאה ל-method‏ onFillRequest().

שירות המילוי האוטומטי בודק אם הוא יכול לענות על הבקשה באמצעות נתוני משתמשים ששמורים בו. אם הוא יכול למלא את הבקשה, אז חבילות השירות הנתונים בDataset אובייקטים. קריאות השירות onSuccess() אמצעי תשלום, העברת FillResponse שמכיל את האובייקטים Dataset. אם השירות לא כאשר יש נתונים למילוי הבקשה, הוא מעביר את null לשיטה onSuccess().

אם מתרחשת שגיאה בעיבוד הבקשה, השירות קורא במקום זאת ל-method‏ 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(). הקוד הבא מציג דוגמה ל-method 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();
}

שירותי מילוי אוטומטי חייבים להצפין מידע אישי רגיש לפני שמאחסנים אותו. עם זאת, נתוני משתמשים יכולים לכלול תוויות או נתונים שאינם רגישים. לדוגמה, משתמש חשבון יכול לכלול תווית שמסמנת את הנתונים כעבודה או כאישי חשבון. אסור להצפין תוויות בשירותים. אם לא מצפינים את התוויות, השירותים יכולים להשתמש בהן בתצוגות של המצגת גם אם המשתמש לא ביצע אימות. לאחר מכן, שירותים יכולים להחליף את התוויות בנתונים בפועל אחרי שהמשתמש מבצע אימות.

דחיית ממשק המשתמש של שמירת המילוי האוטומטי

החל מגרסה 10 של Android, אם משתמשים במספר מסכים כדי להטמיע תהליך עבודה של מילוי אוטומטי – לדוגמה, מסך אחד לשדה שם המשתמש ומסך אחר לסיכה – אפשר לדחות את ממשק המשתמש לשמירת המילוי האוטומטי באמצעות הדגל SaveInfo.FLAG_DELAY_SAVE.

אם הדגל הזה מוגדר, ממשק המשתמש של שמירת המילוי האוטומטי לא מופעל כשהמילוי האוטומטי לא מופעל ההקשר המשויך לתשובה SaveInfo מחויב. במקום זאת, אפשר להשתמש בפעילות נפרדת במסגרת אותה המשימה כדי לשלוח בקשות מילוי עתידיות ואז מציגים את ממשק המשתמש באמצעות בקשה לשמירה. מידע נוסף זמין במאמר הבא: SaveInfo.FLAG_DELAY_SAVE

דרישת אימות של המשתמש

שירותי המילוי האוטומטי יכולים לספק רמת אבטחה נוספת על ידי דרישה משתמש לאימות לפני שיוכל למלא צפיות. התרחישים הבאים הם מועמדים טובים להטמעת אימות משתמשים:

  • כדי לבטל את נעילת נתוני המשתמש באפליקציה, צריך להשתמש בסיסמה ראשית או בסריקת טביעת אצבע.
  • צריך לבטל את הנעילה של מערך נתונים ספציפי, למשל פרטי כרטיס אשראי עד באמצעות קוד אימות כרטיס (CVC).

בתרחיש שבו השירות דורש אימות משתמש לפני ביטול הנעילה את הנתונים, השירות יכול להציג נתונים סטנדרטיים או תווית ולציין Intent שמטפל ב- אימות. אם דרושים נתונים נוספים כדי לעבד את הבקשה לאחר תהליך האימות הסתיים, אפשר להוסיף נתונים כאלה ל-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
פרטי כניסה שם משתמש_עבודה סיסמה_לעבודה
שם משתמש אישי personal_password
כתובת work_street work_city
personal_street personal_city

השירות יכול להכין מערך נתונים שכולל את המחיצה של פרטי הכניסה גם לחשבון העבודה וגם לחשבון האישי. כשהמשתמש בוחר מערך נתונים, תשובה אוטומטית של מילוי אוטומטי יכולה לספק כתובת עבודה או כתובת בהתאם לבחירה הראשונה של המשתמש.

שירות יכול לזהות את השדה שממנו הגיעה הבקשה על ידי קריאה לשיטה isFocused() במהלך סריקה של האובייקט AssistStructure. כך השירות יכול להכין FillResponse עם נתוני המחיצה המתאימים.

מילוי אוטומטי של קוד חד-פעמי ב-SMS

שירות המילוי האוטומטי יכול לעזור למשתמש למלא קודים חד-פעמיים שנשלחים דרך SMS באמצעות ממשק ה-API של SMS Retriever.

כדי להשתמש בתכונה הזו, צריכים להתקיים התנאים הבאים:

  • שירות המילוי האוטומטי פועל ב-Android מגרסה 9 (API ברמה 28) ואילך.
  • המשתמש נותן הסכמה לשירות המילוי האוטומטי לקרוא קודים חד-פעמיים מ-SMS.
  • האפליקציה שבה אתם רוצים להשתמש במילוי אוטומטי לא משתמשת עדיין ב-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 כדי לקבל את התוצאה של קוד ה-SMS כשהיא תהיה זמינה.
  4. כדי להתחיל להאזין לקודי אימות חד-פעמיים שנשלחים בהודעות SMS, צריך להתקשר למספר startSmsCodeRetriever ב-SmsCodeAutofillClient. אם המשתמש מעניק הרשאות לשירות המילוי האוטומטי לאחזר קודים חד-פעמיים מ-SMS, המערכת מחפשת הודעות SMS שהתקבלו ב-1-5 הדקות האחרונות.

    אם שירות המילוי האוטומטי צריך לבקש מהמשתמש הרשאה לקרוא קודים חד-פעמיים, יכול להיות שה-Task שהחזיר startSmsCodeRetriever ייכשל ויוחזר ResolvableApiException. במקרה כזה, צריך להפעיל את השיטה ResolvableApiException.startResolutionForResult() כדי להציג תיבת דו-שיח להבעת הסכמה לבקשת ההרשאה.

  5. קבלת התוצאה של קוד ה-SMS מה-Intent ולאחר מכן להחזיר את ה-SMS כתגובה של מילוי אוטומטי.

תרחישים מתקדמים של מילוי אוטומטי

שילוב עם מקלדת
החל מ-Android 11, הפלטפורמה מאפשרת שימוש במקלדות ועורכי שיטות קלט אחרים (IME) להציג הצעות למילוי אוטומטי בתוך השורה, במקום להשתמש בתפריט נפתח. ניתן לקרוא מידע נוסף על האופן שבו שירות המילוי האוטומטי יכול לתמוך בכך הפונקציונליות הזו זמינה במאמר שילוב המילוי האוטומטי עם מקלדות.
עימוד של מערכי נתונים
תגובה גדולה של מילוי אוטומטי עלולה לחרוג מגודל העסקה המותר של אובייקט 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.

מצב תאימות

מצב התאימות מאפשר לשירותי מילוי אוטומטי להשתמש במבנה הווירטואלי של הנגישות למטרות מילוי אוטומטי. הוא שימושי במיוחד למתן פונקציונליות של מילוי אוטומטי בדפדפנים לא מטמיעים במפורש את ממשקי ה-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.