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

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

ב-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 והמשתמש מפעיל אותה בהגדרות המכשיר. אפליקציה יכולה לבדוק אם היא השירות הפעיל על ידי קריאה ל-method‏ hasEnabledAutofillServices() של המחלקה AutofillManager.

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

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

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

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

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

שירות המילוי האוטומטי בודק אם הוא יכול למלא את הבקשה באמצעות נתוני משתמש שהוא שמר בעבר. אם השירות יכול למלא את הבקשה, הוא אורז את הנתונים באובייקטים של Dataset. השירות קורא ל-method‏ onSuccess() ומעביר אובייקט FillResponse שמכיל את האובייקטים של Dataset. אם לשירות אין נתונים כדי למלא את הבקשה, הוא מעביר את הערך null ל-method‏ 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();
    // ...
}

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

אחרי שהפעילות מאמתת את קוד האימות, היא צריכה להפעיל את השיטה 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);

ארגון הנתונים בקבוצות לוגיות

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

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

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

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

מחיצה שדה 1 שדה 2
פרטי כניסה work_username work_password
personal_username personal_password
כתובת work_street work_city
personal_street personal_city

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

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

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

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

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

  • שירות המילוי האוטומטי פועל ב-Android מגרסה 9 (רמת API‏ 28) ומעלה.
  • המשתמש נותן לשירות המילוי האוטומטי הרשאה לקרוא קודים חד-פעמיים מהודעות SMS.
  • האפליקציה שאתם מספקים לה מילוי אוטומטי לא משתמשת כבר ב-SMS Retriever API כדי לקרוא קודים חד-פעמיים.

שירות המילוי האוטומטי יכול להשתמש ב-SmsCodeAutofillClient, שזמין באמצעות קריאה ל-SmsCodeRetriever.getAutofillClient() מגרסה 19.0.56 ומעלה של Google Play Services.

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

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

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

הפעלת המילוי האוטומטי ב-Chrome

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

הגדרות Chrome שבהן המתג &quot;מילוי אוטומטי באמצעות שירות אחר&quot; מופעל
איור 3. הגדרות Chrome שבהן המתג "מילוי אוטומטי באמצעות שירות אחר" מופעל.

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

כדי לעזור למשתמשים להפעיל את המתג, המפתחים יכולים:

  • שליחת שאילתה להגדרות של Chrome כדי לבדוק אם המשתמש רוצה להשתמש בשירות מילוי אוטומטי של צד שלישי.
  • קישור עומק לדף ההגדרות של Chrome שבו המשתמשים יכולים להפעיל שירותי מילוי אוטומטי של צד שלישי.

הגדרת הגרסאות המקסימליות של Chrome למצב התאימות

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

<autofill-service>
  ...
  <compatibility-package android:name="com.android.chrome" android:maxLongVersionCode="711900039" />
  <compatibility-package android:name="com.chrome.beta" android:maxLongVersionCode="711900039" />
  <compatibility-package android:name="com.chrome.dev" android:maxLongVersionCode="711900039" />
  <compatibility-package android:name="com.chrome.canary" android:maxLongVersionCode="711900039" />
  ...
</autofill-service>

קריאת הגדרות Chrome

כל אפליקציה יכולה לקרוא אם Chrome משתמש במצב המילוי האוטומטי של צד שלישי שמאפשר לו להשתמש במילוי האוטומטי של Android. ‫Chrome משתמש ב-ContentProvider של Android כדי להעביר את המידע הזה. מצהירים במניפסט של Android על הערוצים שמהם רוצים לקרוא את ההגדרות:

<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<queries>
 <!-- To Query Chrome Beta: -->
 <package android:name="com.chrome.beta" />

 <!-- To Query Chrome Stable: -->
 <package android:name="com.android.chrome" />
</queries>

לאחר מכן, משתמשים ב-ContentResolver של Android כדי לבקש את המידע הזה על ידי יצירת ה-URI של התוכן:

Kotlin

val CHROME_CHANNEL_PACKAGE = "com.android.chrome" // Chrome Stable.
val CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider"
val THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state"
val THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode"

val uri = Uri.Builder()
    .scheme(ContentResolver.SCHEME_CONTENT)
    .authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME)
    .path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
    .build()

val cursor = contentResolver.query(
    uri,
    arrayOf(THIRD_PARTY_MODE_COLUMN), // projection
    null, // selection
    null, // selectionArgs
    null  // sortOrder
)

if (cursor == null) {
  // Terminate now! Chromium versions older than this don't provide this information.
}

cursor?.use { // Use the safe call operator and the use function for auto-closing
    if (it.moveToFirst()) { // Check if the cursor has any rows
        val index = it.getColumnIndex(THIRD_PARTY_MODE_COLUMN)
        if (index != -1) { // Check if the column exists
          val value = it.getInt(index)
          if (0 == value) {
              // 0 means that the third party mode is turned off. Chrome uses its built-in
              // password manager. This is the default for new users.
          } else {
              // 1 means that the third party mode is turned on. Chrome forwards all
              // autofill requests to Android Autofill. Users have to opt-in for this.
          }
        } else {
          // Handle the case where the column doesn't exist.  Log a warning, perhaps.
          Log.w("Autofill", "Column $THIRD_PARTY_MODE_COLUMN not found in cursor")
        }
    }
} // The cursor is automatically closed here

Java

final String CHROME_CHANNEL_PACKAGE = "com.android.chrome";  // Chrome Stable.
final String CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider";
final String THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state";
final String THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode";

final Uri uri = new Uri.Builder()
                  .scheme(ContentResolver.SCHEME_CONTENT)
                  .authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME)
                  .path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
                  .build();

final Cursor cursor = getContentResolver().query(
                  uri,
                  /*projection=*/new String[] {THIRD_PARTY_MODE_COLUMN},
                  /*selection=*/ null,
                  /*selectionArgs=*/ null,
                  /*sortOrder=*/ null);

if (cursor == null) {
  // Terminate now! Chromium versions older than this don't provide this information.
}

cursor.moveToFirst(); // Retrieve the result;

int index = cursor.getColumnIndex(THIRD_PARTY_MODE_COLUMN);

if (0 == cursor.getInt(index)) {
  // 0 means that the third party mode is turned off. Chrome uses its built-in
  // password manager. This is the default for new users.
} else {
  // 1 means that the third party mode is turned on. Chrome forwards all
  // autofill requests to Android Autofill. Users have to opt-in for this.
}

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

Kotlin

val autofillSettingsIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES)
autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT)
autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER)
autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE)

// Invoking the intent with a chooser allows users to select the channel they
// want to configure. If only one browser reacts to the intent, the chooser is
// skipped.
val chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel")
startActivity(chooser)

// If the caller knows which Chrome channel they want to configure,
// they can instead add a package hint to the intent, e.g.
val specificChromeIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES) // Create a *new* intent
specificChromeIntent.addCategory(Intent.CATEGORY_DEFAULT)
specificChromeIntent.addCategory(Intent.CATEGORY_APP_BROWSER)
specificChromeIntent.addCategory(Intent.CATEGORY_PREFERENCE)
specificChromeIntent.setPackage("com.android.chrome") // Set the package on the *new* intent
startActivity(specificChromeIntent) // Start the *new* intent

Java

Intent autofillSettingsIntent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES);
autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE);

// Invoking the intent with a chooser allows users to select the channel they
// want to configure. If only one browser reacts to the intent, the chooser is
// skipped.
Intent chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel");
startActivity(chooser);

// If the caller knows which Chrome channel they want to configure,
// they can instead add a package hint to the intent, e.g.
autofillSettingsIntent.setPackage("com.android.chrome");
startActivity(autofillSettingsIntent);

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

כדאי להשתמש במילוי אוטומטי בתרחישים הבאים:

שילוב עם המקלדת

החל מ-Android 11, הפלטפורמה מאפשרת למקלדות ולעורכים אחרים של שיטות קלט (IME) להציג הצעות למילוי אוטומטי בשורה, במקום להשתמש בתפריט נפתח. במאמר שילוב של מילוי אוטומטי עם מקלדות מוסבר איך שירות המילוי האוטומטי יכול לתמוך בפונקציונליות הזו.

עימוד קבוצות נתונים

תגובה גדולה של מילוי אוטומטי יכולה לחרוג מגודל העסקה המותר של האובייקט Binder, שמייצג את האובייקט הניתן להסרה שנדרש כדי לעבד את הבקשה. כדי למנוע ממערכת Android להציג חריגה בתרחישים האלה, אפשר לשמור על FillResponse קטן על ידי הוספה של לא יותר מ-20 אובייקטים של Dataset בכל פעם. אם התשובה צריכה להכיל עוד קבוצות נתונים, אתם יכולים להוסיף קבוצת נתונים שמודיעה למשתמשים שיש עוד מידע, ושולפת את הקבוצה הבאה של קבוצות הנתונים כשהמשתמשים בוחרים בה. מידע נוסף זמין במאמר addDataset(Dataset).

שמירת נתונים שמפוצלים לכמה מסכים

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

  1. בבקשת המילוי הראשונה, מוסיפים לתגובה חבילת מצב לקוח שמכילה את מזהי המילוי האוטומטי של השדות החלקיים שמופיעים במסך.
  2. בבקשת המילוי השנייה, מאחזרים את חבילת מצב הלקוח, מקבלים את מזהי המילוי האוטומטי שהוגדרו בבקשה הקודמת ממצב הלקוח, ומוסיפים את המזהים האלה ואת הדגל FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE לאובייקט SaveInfo שמשמש בתגובה השנייה.
  3. בבקשת השמירה, משתמשים באובייקטים המתאימים של FillContext כדי לקבל את הערך של כל שדה. לכל בקשת מילוי יש הקשר מילוי אחד.

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

לוגיקה של אתחול ופירוק לכל בקשה

בכל פעם שיש בקשה למילוי אוטומטי, מערכת Android נקשרת לשירות וקוראת ל-method‏ 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 או כשמתבצעת קריאה ל-method‏ setTrigger(). הערך FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE מוגדר כברירת מחדל כשמשתמשים במצב תאימות.
  • יכול להיות שערך הטקסט של הצמתים לא יהיה זמין ב-method‏ onSaveRequest(SaveRequest, SaveCallback).

למידע נוסף על מצב תאימות, כולל המגבלות שקשורות אליו, עיינו בחומר העזר בנושא המחלקה AutofillService.