[Android][3PPWM] Example AutofillService implementation
This is not a part of Chromium and users shouldn't need to interact with this test app. This prototype helps debugging how AutofillServices see viewStructures generated by Chromium. The dependency to androidx.autofill landed in crrev.com/c/6172794. Until androidx rollers picked it up, refetch the androidx dependency by executing: `python3 third_party/androidx/fetch_all_androidx.py`. Change-Id: I87b680460ace16c3619e2ba90b5a1cd87a55d388 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6076641 Commit-Queue: Friedrich Hauser <friedrichh@chromium.org> Reviewed-by: Ivana Žužić <izuzic@google.com> Cr-Commit-Position: refs/heads/main@{#1407206}


1
BUILD.gn
@ -435,6 +435,7 @@ group("gn_all") {
|
||||
"//chrome/android:chrome_public_test_apk",
|
||||
"//chrome/android:chrome_public_unit_test_apk",
|
||||
"//chrome/browser/android/examples/custom_tabs_client:custom_tabs_client_example_apk",
|
||||
"//chrome/browser/android/examples/inline_autofill_service:inline_autofill_service_example_apk",
|
||||
"//chrome/browser/android/examples/partner_browser_customizations_provider:partner_browser_customizations_example_apk",
|
||||
"//content/shell/android:content_shell_test_apk",
|
||||
]
|
||||
|
@ -0,0 +1,87 @@
|
||||
# Copyright 2025 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import("//build/config/android/rules.gni")
|
||||
|
||||
android_resources("inline_autofill_service_example_apk_resources") {
|
||||
sources = [
|
||||
"src/res/drawable/cr_fill.xml",
|
||||
"src/res/layout/activity_main.xml",
|
||||
"src/res/layout/activity_settings.xml",
|
||||
"src/res/layout/fragment_browser_communication.xml",
|
||||
"src/res/layout/fragment_instructions.xml",
|
||||
"src/res/layout/list_item.xml",
|
||||
"src/res/menu/menu_main.xml",
|
||||
"src/res/mipmap-anydpi/ic_launcher.xml",
|
||||
"src/res/mipmap-anydpi/ic_launcher_round.xml",
|
||||
"src/res/mipmap-hdpi/ic_launcher.png",
|
||||
"src/res/mipmap-hdpi/ic_launcher_foreground.png",
|
||||
"src/res/mipmap-hdpi/ic_launcher_round.png",
|
||||
"src/res/mipmap-ldpi/ic_launcher.png",
|
||||
"src/res/mipmap-mdpi/ic_launcher.png",
|
||||
"src/res/mipmap-mdpi/ic_launcher_foreground.png",
|
||||
"src/res/mipmap-mdpi/ic_launcher_round.png",
|
||||
"src/res/mipmap-xhdpi/ic_launcher.png",
|
||||
"src/res/mipmap-xhdpi/ic_launcher_foreground.png",
|
||||
"src/res/mipmap-xhdpi/ic_launcher_round.png",
|
||||
"src/res/mipmap-xxhdpi/ic_launcher.png",
|
||||
"src/res/mipmap-xxhdpi/ic_launcher_foreground.png",
|
||||
"src/res/mipmap-xxhdpi/ic_launcher_round.png",
|
||||
"src/res/mipmap-xxxhdpi/ic_launcher.png",
|
||||
"src/res/mipmap-xxxhdpi/ic_launcher_foreground.png",
|
||||
"src/res/mipmap-xxxhdpi/ic_launcher_round.png",
|
||||
"src/res/values-land/dimens.xml",
|
||||
"src/res/values-night/themes.xml",
|
||||
"src/res/values-v23/themes.xml",
|
||||
"src/res/values-w1240dp/dimens.xml",
|
||||
"src/res/values-w600dp/dimens.xml",
|
||||
"src/res/values/colors.xml",
|
||||
"src/res/values/dimens.xml",
|
||||
"src/res/values/strings.xml",
|
||||
"src/res/values/themes.xml",
|
||||
"src/res/xml/autofill_service_config.xml",
|
||||
]
|
||||
android_manifest = "src/AndroidManifest.xml"
|
||||
deps = [ "//third_party/android_deps:material_design_java" ]
|
||||
}
|
||||
|
||||
android_apk("inline_autofill_service_example_apk") {
|
||||
sources = [
|
||||
"src/java/org/chromium/example/autofill_service/MainActivity.java",
|
||||
"src/java/org/chromium/example/autofill_service/SettingsActivity.java",
|
||||
"src/java/org/chromium/example/autofill_service/fill_service/InlineFillService.java",
|
||||
"src/java/org/chromium/example/autofill_service/fill_service/helpers/AttributionDialogActivity.java",
|
||||
"src/java/org/chromium/example/autofill_service/fill_service/helpers/InlineRequestHelper.java",
|
||||
"src/java/org/chromium/example/autofill_service/fill_service/helpers/ResponseHelper.java",
|
||||
"src/java/org/chromium/example/autofill_service/fill_service/helpers/ViewStructureParser.java",
|
||||
"src/java/org/chromium/example/autofill_service/fragments/BrowserCommunicationFragment.java",
|
||||
"src/java/org/chromium/example/autofill_service/fragments/InstructionsFragment.java",
|
||||
]
|
||||
|
||||
android_manifest = "src/AndroidManifest.xml"
|
||||
apk_name = "ChromiumExampleAutofillService"
|
||||
|
||||
deps = [
|
||||
":inline_autofill_service_example_apk_resources",
|
||||
"//third_party/android_deps:material_design_java",
|
||||
"//third_party/androidx:androidx_activity_activity_java",
|
||||
"//third_party/androidx:androidx_annotation_annotation_java",
|
||||
"//third_party/androidx:androidx_appcompat_appcompat_java",
|
||||
"//third_party/androidx:androidx_autofill_autofill_java",
|
||||
"//third_party/androidx:androidx_browser_browser_java",
|
||||
"//third_party/androidx:androidx_fragment_fragment_java",
|
||||
|
||||
# implementation "androidx.autofill:autofill:1.1.0"
|
||||
# implementation libs.appcompat
|
||||
# implementation libs.material
|
||||
# implementation libs.constraintlayout
|
||||
# implementation libs.navigation.fragment
|
||||
# implementation libs.navigation.ui
|
||||
]
|
||||
chromium_code = false
|
||||
never_incremental = true
|
||||
|
||||
#min_sdk_version = 33
|
||||
#target_sdk_version = 34
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
monorail: {
|
||||
component: "UI>Browser>Autofill"
|
||||
}
|
||||
buganizer_public: {
|
||||
component_id: 1456764
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
friedrichh@chromium.org
|
||||
izuzic@google.com
|
||||
|
||||
file://components/android_autofill/OWNERS
|
@ -0,0 +1,57 @@
|
||||
# Testing Chromium with AutofillServices
|
||||
|
||||
[TOC]
|
||||
|
||||
Autofill Services provide Autofill data for all apps. In Chromium, a built-in
|
||||
service provides Autofill data across all platforms. Users can switch to using
|
||||
the device-wide Autofill Service. This example implementation is used to test
|
||||
Chromium with a primitive implementation.
|
||||
|
||||
## What does this Autofill Service do?
|
||||
|
||||
It provides an AutofillService which provides static data for each field by
|
||||
echoing the type of field and a number. It allows filling simple forms.
|
||||
This Service supports inline suggestions.
|
||||
|
||||
The main activity explains how to set the service in settings and provides
|
||||
additional information.
|
||||
|
||||
It should help to understand how an app can interact with Chrome using intents
|
||||
and ContentProviders.
|
||||
|
||||
## Building
|
||||
|
||||
These instruction assume that you have already built Chromium for Android. If
|
||||
not, instructions for building Chromium for Android are
|
||||
[here](/docs/android_build_instructions.md). Details below assume that the
|
||||
build is setup in `$CHROMIUM_OUTPUT_DIR`.
|
||||
|
||||
### Build the Chromium Test Autofill App
|
||||
|
||||
To build the test app and the AutofillService, execute:
|
||||
|
||||
```shell
|
||||
$ autoninja -C $CHROMIUM_OUTPUT_DIR inline_autofill_service_example_apk
|
||||
```
|
||||
|
||||
### Install the Chromium Test Autofill App
|
||||
|
||||
To install the test app and the AutofillService, execute:
|
||||
|
||||
```shell
|
||||
# Install the example
|
||||
$ $CHROMIUM_OUTPUT_DIR/bin/inline_autofill_service_example_apk install
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Using the Autofill Service
|
||||
|
||||
The Autofill Service has to be enabled in Android settings. Select it as
|
||||
Autofill provider.
|
||||
Additionally, enable in Chromium settings > Autofill Services that the external
|
||||
service may be used. Restart Chromium.
|
||||
|
||||
### Using the Test App
|
||||
|
||||
The test app appears in the app drawer and can be started from there.
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.chromium.example.autofill_service"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.ChromiumInlineSuggestionsAutofillService">
|
||||
<activity
|
||||
android:name="org.chromium.example.autofill_service.MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.ChromiumInlineSuggestionsAutofillService">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="org.chromium.example.autofill_service.fill_service.InlineFillService"
|
||||
android:exported="true"
|
||||
android:label="@string/service_name"
|
||||
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/autofill_service_config"/>
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name="org.chromium.example.autofill_service.SettingsActivity"
|
||||
android:exported="true"
|
||||
android:label="Autofill Settings" />
|
||||
|
||||
<activity
|
||||
android:name="org.chromium.example.autofill_service.fill_service.helpers.AttributionDialogActivity"
|
||||
android:label="Autofill Attribution"
|
||||
android:theme="@android:style/Theme.Material.Light.Dialog.NoActionBar" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
84
chrome/browser/android/examples/inline_autofill_service/src/java/org/chromium/example/autofill_service/MainActivity.java
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
/**
|
||||
* Initial page for the installed app. It's unrelated to the AutofillService but provides tools and
|
||||
* instructions on how to set it up.
|
||||
*/
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
|
||||
findViewById(R.id.fab)
|
||||
.setOnClickListener(
|
||||
v -> {
|
||||
Intent sendIntent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES);
|
||||
sendIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
|
||||
sendIntent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
sendIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
|
||||
sendIntent.addCategory(Intent.CATEGORY_PREFERENCE);
|
||||
|
||||
// Try to invoke the intent.
|
||||
try {
|
||||
Intent chooser =
|
||||
Intent.createChooser(sendIntent, "Pick Chrome Channel");
|
||||
startActivity(chooser);
|
||||
Snackbar.make(v, "Triggered the intent", Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(R.id.fab)
|
||||
.setAction("Action", null)
|
||||
.show();
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Snackbar.make(v, "Activity not found", Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(R.id.fab)
|
||||
.setAction("Action", null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (id == R.id.action_settings) {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
}
|
19
chrome/browser/android/examples/inline_autofill_service/src/java/org/chromium/example/autofill_service/SettingsActivity.java
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* A sample settings activity that opens on clicking the inline suggestion icon. It does nothing.
|
||||
*/
|
||||
public class SettingsActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_settings);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service.fill_service;
|
||||
|
||||
import android.os.CancellationSignal;
|
||||
import android.service.autofill.AutofillService;
|
||||
import android.service.autofill.FillCallback;
|
||||
import android.service.autofill.FillRequest;
|
||||
import android.service.autofill.SaveCallback;
|
||||
import android.service.autofill.SaveRequest;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.chromium.example.autofill_service.fill_service.helpers.ResponseHelper;
|
||||
|
||||
/**
|
||||
* A basic {@link AutofillService} implementation that only shows dynamic-generated datasets and
|
||||
* supports inline suggestions.
|
||||
*/
|
||||
public class InlineFillService extends AutofillService {
|
||||
|
||||
@Override
|
||||
public void onFillRequest(
|
||||
FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) {
|
||||
callback.onSuccess(ResponseHelper.createSimpleResponse(this, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
|
||||
Toast.makeText(this, "InlineFillService doesn't support Save", Toast.LENGTH_LONG).show();
|
||||
callback.onSuccess();
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service.fill_service.helpers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.chromium.example.autofill_service.SettingsActivity;
|
||||
|
||||
/**
|
||||
* Long-pressing an inline suggestion opens this activity. It shows a dialog describing the source
|
||||
* of the suggestion.
|
||||
*/
|
||||
public class AttributionDialogActivity extends Activity {
|
||||
static final String KEY_MSG = "AttributionDialogActivity:msg";
|
||||
static final String DEFAULT_MSG = "Hello";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
|
||||
final Intent intent = getIntent();
|
||||
Dialog dialog = createDialog(intent != null ? intent.getStringExtra(KEY_MSG) : DEFAULT_MSG);
|
||||
dialog.setOnDismissListener(d -> finish());
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private Dialog createDialog(String msg) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage("The suggestions are generated by the InlineFillService. " + msg)
|
||||
.setNegativeButton(
|
||||
"Settings",
|
||||
(dialog, id) -> {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
})
|
||||
.setPositiveButton(
|
||||
"Got it",
|
||||
(dialog, id) -> {
|
||||
// User cancelled the dialog
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service.fill_service.helpers;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.slice.Slice;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.FillRequest;
|
||||
import android.service.autofill.InlinePresentation;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||
import android.widget.inline.InlinePresentationSpec;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.autofill.inline.v1.InlineSuggestionUi;
|
||||
import androidx.autofill.inline.v1.InlineSuggestionUi.Content;
|
||||
|
||||
import org.chromium.example.autofill_service.SettingsActivity;
|
||||
|
||||
class InlineRequestHelper {
|
||||
|
||||
private final @Nullable InlineSuggestionsRequest mInlineRequest;
|
||||
private final Context mContext;
|
||||
|
||||
InlineRequestHelper(Context context, FillRequest fillRequest) {
|
||||
mContext = context;
|
||||
mInlineRequest = getInlineSuggestionsRequest(fillRequest);
|
||||
}
|
||||
|
||||
private static @Nullable InlineSuggestionsRequest getInlineSuggestionsRequest(
|
||||
FillRequest request) {
|
||||
final InlineSuggestionsRequest inlineRequest = request.getInlineSuggestionsRequest();
|
||||
if (inlineRequest != null
|
||||
&& inlineRequest.getMaxSuggestionCount() > 0
|
||||
&& !inlineRequest.getInlinePresentationSpecs().isEmpty()) {
|
||||
return inlineRequest;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int restrictMaxSuggestionCount(int max) {
|
||||
return mInlineRequest != null ? Math.min(max, mInlineRequest.getMaxSuggestionCount()) : max;
|
||||
}
|
||||
|
||||
boolean hasInlineRequest() {
|
||||
return mInlineRequest != null;
|
||||
}
|
||||
|
||||
InlinePresentation createInlineDataset(String value, int index) {
|
||||
assert hasInlineRequest();
|
||||
final PendingIntent attribution =
|
||||
createAttribution("Please tap on the chip to autofill the value:" + value);
|
||||
final Slice slice = createSlice(value, null, attribution);
|
||||
index = Math.min(mInlineRequest.getInlinePresentationSpecs().size() - 1, index);
|
||||
final InlinePresentationSpec spec = mInlineRequest.getInlinePresentationSpecs().get(index);
|
||||
return new InlinePresentation(slice, spec, false);
|
||||
}
|
||||
|
||||
Dataset createInlineActionDataset(ArrayMap<String, AutofillId> fields, int drawable) {
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
mContext,
|
||||
0,
|
||||
new Intent(mContext, SettingsActivity.class),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
Dataset.Builder builder =
|
||||
new Dataset.Builder()
|
||||
.setInlinePresentation(createInlineAction(drawable))
|
||||
.setAuthentication(pendingIntent.getIntentSender());
|
||||
for (AutofillId fieldId : fields.values()) {
|
||||
builder.setValue(fieldId, null);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private InlinePresentation createInlineAction(int drawable) {
|
||||
assert hasInlineRequest();
|
||||
return new InlinePresentation(
|
||||
createSlice(
|
||||
null,
|
||||
Icon.createWithResource(mContext, drawable),
|
||||
createAttribution("Please tap on the chip to launch the action.")),
|
||||
mInlineRequest.getInlinePresentationSpecs().get(0), // Reuse first spec's height.
|
||||
true);
|
||||
}
|
||||
|
||||
private PendingIntent createAttribution(String msg) {
|
||||
Intent intent = new Intent(mContext, AttributionDialogActivity.class);
|
||||
intent.putExtra(AttributionDialogActivity.KEY_MSG, msg);
|
||||
return PendingIntent.getActivity(
|
||||
mContext,
|
||||
msg.hashCode(), // Different request code avoids overriding previous intents.
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
}
|
||||
|
||||
private static Slice createSlice(String title, Icon startIcon, PendingIntent attribution) {
|
||||
Content.Builder builder = InlineSuggestionUi.newContentBuilder(attribution);
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title);
|
||||
}
|
||||
if (startIcon != null) {
|
||||
builder.setStartIcon(startIcon);
|
||||
}
|
||||
return builder.build().getSlice();
|
||||
}
|
||||
}
|
112
chrome/browser/android/examples/inline_autofill_service/src/java/org/chromium/example/autofill_service/fill_service/helpers/ResponseHelper.java
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service.fill_service.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.Field;
|
||||
import android.service.autofill.FillRequest;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.service.autofill.Presentations;
|
||||
import android.service.autofill.SaveInfo;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillValue;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.chromium.example.autofill_service.R;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/** Helps to build simple responses to {@link FillRequests}. */
|
||||
public class ResponseHelper {
|
||||
|
||||
private static final int NUMBER_DATASETS = 6; // Fixed number of datasets sent on each request.
|
||||
private final Context mContext;
|
||||
private final InlineRequestHelper mInlineHelper;
|
||||
|
||||
public static @Nullable FillResponse createSimpleResponse(
|
||||
Context context, FillRequest request) {
|
||||
final ArrayMap<String, AutofillId> fields =
|
||||
ViewStructureParser.findAutofillableFields(request);
|
||||
if (fields.isEmpty()) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"InlineFillService could not figure out how to autofill this screen",
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return null; // Valid value to pass to FillCallback: It signals no suggestions.
|
||||
}
|
||||
|
||||
return new ResponseHelper(context, new InlineRequestHelper(context, request))
|
||||
.buildResponse(fields);
|
||||
}
|
||||
|
||||
private ResponseHelper(Context context, InlineRequestHelper inlineRequestHelper) {
|
||||
mContext = context;
|
||||
mInlineHelper = inlineRequestHelper;
|
||||
}
|
||||
|
||||
private int getDatasetCount() {
|
||||
return mInlineHelper.restrictMaxSuggestionCount(NUMBER_DATASETS);
|
||||
}
|
||||
|
||||
private FillResponse buildResponse(ArrayMap<String, AutofillId> fields) {
|
||||
|
||||
FillResponse.Builder response = new FillResponse.Builder();
|
||||
|
||||
for (int i = 0; i < getDatasetCount(); i++) {
|
||||
response.addDataset(newUnlockedDataset(fields, i));
|
||||
}
|
||||
|
||||
if (mInlineHelper.hasInlineRequest()) {
|
||||
response.addDataset(
|
||||
mInlineHelper.createInlineActionDataset(fields, R.drawable.cr_fill));
|
||||
}
|
||||
|
||||
response.setSaveInfo(createSaveInfoForIds(fields.values()));
|
||||
|
||||
return response.build();
|
||||
}
|
||||
|
||||
private Dataset newUnlockedDataset(Map<String, AutofillId> fields, int index) {
|
||||
Dataset.Builder dataset = new Dataset.Builder();
|
||||
fields.forEach((hint, id) -> dataset.setField(id, createField(index, hint)));
|
||||
return dataset.build();
|
||||
}
|
||||
|
||||
private Field createField(int index, String hint) {
|
||||
final String value = hint + (index + 1);
|
||||
final String displayValue =
|
||||
hint.contains("password") ? "password for #" + (index + 1) : value;
|
||||
Presentations.Builder presentationsBuilder =
|
||||
new Presentations.Builder()
|
||||
.setDialogPresentation(newDatasetPresentation(displayValue));
|
||||
if (mInlineHelper.hasInlineRequest()) {
|
||||
presentationsBuilder.setInlinePresentation(
|
||||
mInlineHelper.createInlineDataset(displayValue, index));
|
||||
}
|
||||
return new Field.Builder()
|
||||
.setPresentations(presentationsBuilder.build())
|
||||
.setValue(AutofillValue.forText(value))
|
||||
.build();
|
||||
}
|
||||
|
||||
private RemoteViews newDatasetPresentation(CharSequence text) {
|
||||
RemoteViews presentation = new RemoteViews(mContext.getPackageName(), R.layout.list_item);
|
||||
presentation.setTextViewText(R.id.text, text);
|
||||
return presentation;
|
||||
}
|
||||
|
||||
private static SaveInfo createSaveInfoForIds(Collection<AutofillId> ids) {
|
||||
AutofillId[] requiredIds = new AutofillId[ids.size()];
|
||||
ids.toArray(requiredIds);
|
||||
return new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build();
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service.fill_service.helpers;
|
||||
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.service.autofill.FillContext;
|
||||
import android.service.autofill.FillRequest;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.autofill.AutofillId;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class ViewStructureParser {
|
||||
/** Extracts the autofillable fields from the request through assist structure. */
|
||||
static ArrayMap<String, AutofillId> findAutofillableFields(@NonNull FillRequest request) {
|
||||
return findAutofillableFields(getLatestAssistStructure(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewStructureParser method to get the {@link AssistStructure} associated with the latest
|
||||
* request in an autofill context.
|
||||
*/
|
||||
@NonNull
|
||||
private static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) {
|
||||
List<FillContext> fillContexts = request.getFillContexts();
|
||||
return fillContexts.get(fillContexts.size() - 1).getStructure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the {@link AssistStructure} representing the activity being autofilled, and returns a
|
||||
* map of autofillable fields (represented by their autofill ids) mapped by the hint associate
|
||||
* with them.
|
||||
*
|
||||
* <p>An autofillable field is a {@link AssistStructure.ViewNode} whose getHint(ViewNode)
|
||||
* method.
|
||||
*/
|
||||
@NonNull
|
||||
private static ArrayMap<String, AutofillId> findAutofillableFields(
|
||||
@NonNull AssistStructure structure) {
|
||||
ArrayMap<String, AutofillId> fields = new ArrayMap<>();
|
||||
int nodes = structure.getWindowNodeCount();
|
||||
for (int i = 0; i < nodes; i++) {
|
||||
AssistStructure.ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
|
||||
addAutofillableFields(fields, node);
|
||||
}
|
||||
ArrayMap<String, AutofillId> result = new ArrayMap<>();
|
||||
int filedCount = fields.size();
|
||||
for (int i = 0; i < filedCount; i++) {
|
||||
String key = fields.keyAt(i);
|
||||
AutofillId value = fields.valueAt(i);
|
||||
// For fields with no hint we just use Field
|
||||
if (key.equals(value.toString())) {
|
||||
result.put("Field:" + i + "-", fields.valueAt(i));
|
||||
} else {
|
||||
result.put(key, fields.valueAt(i));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any autofillable view from the {@link AssistStructure.ViewNode} and its descendants to
|
||||
* the map.
|
||||
*/
|
||||
private static void addAutofillableFields(
|
||||
@NonNull Map<String, AutofillId> fields, @NonNull AssistStructure.ViewNode node) {
|
||||
if (node.getAutofillType() == View.AUTOFILL_TYPE_TEXT) {
|
||||
if (!fields.containsValue(node.getAutofillId())) {
|
||||
fields.put(getFieldKey(fields, node), node.getAutofillId());
|
||||
}
|
||||
}
|
||||
int childrenSize = node.getChildCount();
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
addAutofillableFields(fields, node.getChildAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFieldKey(
|
||||
@NonNull Map<String, AutofillId> fields, AssistStructure.ViewNode node) {
|
||||
if (!TextUtils.isEmpty(node.getHint())) {
|
||||
final String key = node.getHint().toLowerCase();
|
||||
if (!fields.containsKey(key)) return key;
|
||||
}
|
||||
if (node.getAutofillHints() != null && node.getAutofillHints().length > 0) {
|
||||
final String key = node.getAutofillHints()[0].toLowerCase();
|
||||
if (!fields.containsKey(key)) return key;
|
||||
}
|
||||
String name = null;
|
||||
String id = null;
|
||||
if (node.getHtmlInfo() != null) {
|
||||
for (Pair<String, String> kv : node.getHtmlInfo().getAttributes()) {
|
||||
if ("type".equals(kv.first.toLowerCase())) {
|
||||
return kv.second.toLowerCase();
|
||||
}
|
||||
if ("name".equals(kv.first.toLowerCase())) {
|
||||
name = kv.second.toLowerCase();
|
||||
}
|
||||
if ("id".equals(kv.first.toLowerCase())) {
|
||||
id = kv.second.toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(name)) return name;
|
||||
if (!TextUtils.isEmpty(id)) return id;
|
||||
return node.getAutofillId().toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.chromium.example.autofill_service.R;
|
||||
|
||||
/** Secondary fragment for the main page. It explains how an app can communicate with Chromium. */
|
||||
public class BrowserCommunicationFragment extends Fragment {
|
||||
public BrowserCommunicationFragment() {
|
||||
super(R.layout.fragment_browser_communication);
|
||||
}
|
||||
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
view.findViewById(R.id.button_second)
|
||||
.setOnClickListener(
|
||||
v -> {
|
||||
getParentFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(
|
||||
R.id.fragment_container_view,
|
||||
InstructionsFragment.class,
|
||||
null)
|
||||
.setReorderingAllowed(true)
|
||||
.disallowAddToBackStack()
|
||||
.commit();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.example.autofill_service.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.chromium.example.autofill_service.R;
|
||||
|
||||
/** Primary fragment of the landing page. It describes the setup of the bundled AutofillService. */
|
||||
public class InstructionsFragment extends Fragment {
|
||||
public InstructionsFragment() {
|
||||
super(R.layout.fragment_instructions);
|
||||
}
|
||||
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
view.findViewById(R.id.button_first)
|
||||
.setOnClickListener(
|
||||
v -> {
|
||||
getParentFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(
|
||||
R.id.fragment_container_view,
|
||||
BrowserCommunicationFragment.class,
|
||||
null)
|
||||
.setReorderingAllowed(true)
|
||||
.addToBackStack("name") // Name can be null
|
||||
.commit();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M151.8,2.04l100.95,0l0,100.95L151.8,102.99z"/>
|
||||
<path
|
||||
android:pathData="M202.28,77.75c13.94,0 25.24,-11.3 25.24,-25.24 0,-13.94 -11.3,-25.24 -25.24,-25.24 -13.94,0 -25.24,11.3 -25.24,25.24 0,13.94 11.3,25.24 25.24,25.24Z"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="M189.67,74.37a25.11,25.11 45,0 1,-9.24 -9.24l-0,0 -21.86,-37.86a50.49,50.49 0,0 0,43.71 75.72l21.86,-37.87a25.23,25.23 0,0 1,-6.49 7.4,25.24 25.24,45 0,1 -27.98,1.83Z"
|
||||
android:fillColor="#669DF6"/>
|
||||
<path
|
||||
android:pathData="M227.52,52.51a25.11,25.11 0,0 1,-3.38 12.62l0,0 -21.86,37.86a50.49,50.49 0,0 0,43.72 -75.72L202.28,27.27a25.24,25.24 0,0 1,25.24 25.24Z"
|
||||
android:fillColor="#AECBFA"/>
|
||||
<path
|
||||
android:pathData="M202.28,73.02c11.33,0 20.51,-9.18 20.51,-20.51s-9.18,-20.51 -20.51,-20.51 -20.51,9.18 -20.51,20.51 9.18,20.51 20.51,20.51Z"
|
||||
android:fillColor="#1A73E8"/>
|
||||
<path
|
||||
android:pathData="M189.66,30.66a25.11,25.11 0,0 1,12.62 -3.38l0,-0l43.71,0A50.49,50.49 0,0 0,202.28 2.05,50.49 50.49,45 0,0 158.56,27.28l21.86,37.87a25.24,25.24 45,0 1,1.83 -27.98A25.24,25.24 66.96,0 1,189.66 30.66Z"
|
||||
android:fillColor="#1967D2"/>
|
||||
</group>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M146.72,41.21L155.48,31.53C123.98,70.1 179.47,143.23 238.47,92.39L221.41,106.68L114.44,216.42L47.59,153.25C47.59,153.25 149.02,40.75 146.72,41.21Z"
|
||||
android:fillColor="#669DF6"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M45.74,156.48L111.68,218.72L72.95,234.4L71.56,221.95L55.43,226.56L57.27,201.66L48.51,204.89L50.35,186.91C50.35,186.91 32.83,198.44 33.29,198.44C33.75,198.44 45.28,156.48 45.74,156.48Z"
|
||||
android:fillColor="#AECBFA"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M32.37,203.05L15.77,251.46L68.8,234.86L68.34,226.56L52.66,230.71L54.04,207.2L43.44,210.89L47.13,194.29L32.37,203.05Z"
|
||||
android:fillColor="#1A73E8"
|
||||
android:strokeColor="#000000"/>
|
||||
</vector>
|
44
chrome/browser/android/examples/inline_autofill_service/src/res/layout/activity_main.xml
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="org.chromium.example.autofill_service.MainActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment_container_view"
|
||||
android:layout_marginTop="80dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:name="org.chromium.example.autofill_service.fragments.InstructionsFragment" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginEnd="@dimen/fab_margin"
|
||||
android:layout_gravity="bottom|end"
|
||||
app:srcCompat="@android:drawable/ic_dialog_email" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
19
chrome/browser/android/examples/inline_autofill_service/src/res/layout/activity_settings.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAutofill="noExcludeDescendants"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical" >
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_msg" />
|
||||
</LinearLayout>
|
30
chrome/browser/android/examples/inline_autofill_service/src/res/layout/fragment_browser_communication.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="org.chromium.example.autofill_service.fragments.BrowserCommunicationFragment">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<Button
|
||||
android:id="@+id/button_second"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/show_instructions"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textview_second"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/fragment_browser_communication_summary" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
29
chrome/browser/android/examples/inline_autofill_service/src/res/layout/fragment_instructions.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="org.chromium.example.autofill_service.fragments.InstructionsFragment">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<Button
|
||||
android:id="@+id/button_first"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/comms_test"/>
|
||||
<TextView
|
||||
android:id="@+id/textview_first"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/fragment_instructions_summary" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="#ffffffff">
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall">
|
||||
</TextView>
|
||||
</LinearLayout>
|
@ -0,0 +1,15 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="org.chromium.example.autofill_service.MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_settings"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
10
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-anydpi/ic_launcher.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
10
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-anydpi/ic_launcher_round.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-hdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 4.3 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 8.7 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 6.4 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-ldpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 1.6 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-mdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 2.1 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 4.6 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 3.4 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 4.4 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 11 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 7.5 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 10 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 37 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 15 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After ![]() (image error) Size: 12 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
After ![]() (image error) Size: 51 KiB |
BIN
chrome/browser/android/examples/inline_autofill_service/src/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After ![]() (image error) Size: 18 KiB |
8
chrome/browser/android/examples/inline_autofill_service/src/res/values-land/dimens.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources>
|
||||
<dimen name="fab_margin">48dp</dimen>
|
||||
</resources>
|
9
chrome/browser/android/examples/inline_autofill_service/src/res/values-night/themes.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources>
|
||||
<style name="Base.Theme.TestAutofillService" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
</style>
|
||||
</resources>
|
14
chrome/browser/android/examples/inline_autofill_service/src/res/values-v23/themes.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="Theme.TestAutofillService" parent="Base.Theme.TestAutofillService">
|
||||
<!-- Transparent system bars for edge-to-edge. -->
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
|
||||
</style>
|
||||
</resources>
|
8
chrome/browser/android/examples/inline_autofill_service/src/res/values-w1240dp/dimens.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources>
|
||||
<dimen name="fab_margin">200dp</dimen>
|
||||
</resources>
|
8
chrome/browser/android/examples/inline_autofill_service/src/res/values-w600dp/dimens.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources>
|
||||
<dimen name="fab_margin">48dp</dimen>
|
||||
</resources>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fafcfe</color>
|
||||
</resources>
|
@ -0,0 +1,8 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
@ -0,0 +1,35 @@
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources>
|
||||
<string name="app_name">Chromium Test Autofill App</string>
|
||||
<string name="service_name">Chromium Test Autofill Service</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="fragment_instructions_label">Instructions Fragment</string>
|
||||
<string name="fragment_browser_communication_label">Browser Communication Fragment</string>
|
||||
<string name="comms_test">Test Communication</string>
|
||||
<string name="show_instructions">Show Instructions</string>
|
||||
|
||||
<string name="fragment_instructions_summary">
|
||||
This test app comes with an Autofill Service providing static data.\n
|
||||
\n
|
||||
In order to use it\:\n
|
||||
a. Enable it in Android System settings, and\n
|
||||
b. Enable the use of Android Autofill Services in Chrome settings.\n
|
||||
\n
|
||||
Click the button at the top to check how this app can communicate with the
|
||||
browser.\n
|
||||
\n
|
||||
By default, this app does not use the compatibility mode to fill Chrome.
|
||||
Change the autofill_service_config.xml and recompile this app to allow it.
|
||||
</string>
|
||||
<string name="fragment_browser_communication_summary">
|
||||
Besides the Autofill service, this test app may communicate with Chrome.\n
|
||||
\n
|
||||
TODO(izuzic): Add instructions for ContentProvider usage and read data here.
|
||||
TODO(izuzic): Add explanation and deep-link to Chrome\'s setting here.
|
||||
</string>
|
||||
<string name="settings_msg">Nothing to see here. This app doesn\'t have settings.</string>
|
||||
</resources>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="Theme.ChromiumInlineSuggestionsAutofillService" parent="Theme.AppCompat.DayNight.NoActionBar" />
|
||||
</resources>
|
19
chrome/browser/android/examples/inline_autofill_service/src/res/xml/autofill_service_config.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2025 The Chromium Authors
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file.-->
|
||||
|
||||
|
||||
<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:supportsInlineSuggestions="true"
|
||||
android:settingsActivity="org.chromium.example.android_autofill.SettingsActivity"
|
||||
android:passwordsActivity="org.chromium.example.android_autofill.MainActivity">
|
||||
<!-- Uncomment any chrome version below to use enable autofill via accessibility.
|
||||
<compatibility-package android:name="com.android.chrome" android:maxLongVersionCode="1000000000" />
|
||||
<compatibility-package android:name="com.chrome.beta" android:maxLongVersionCode="1000000000" />
|
||||
<compatibility-package android:name="com.chrome.dev" android:maxLongVersionCode="1000000000" />
|
||||
<compatibility-package android:name="com.chrome.canary" android:maxLongVersionCode="1000000000" />
|
||||
<compatibility-package android:name="com.google.android.apps.chrome" android:maxLongVersionCode="1000000000" />
|
||||
-->
|
||||
</autofill-service>
|