aboutsummaryrefslogtreecommitdiff
path: root/input/autofill/AutofillFramework
diff options
context:
space:
mode:
authorFelipe Leme <felipeal@google.com>2018-10-04 13:39:26 -0700
committerFelipe Leme <felipeal@google.com>2018-10-10 11:58:36 -0700
commit5367691cf1d3f357b70f2488234e5b2442885c14 (patch)
tree9781e6d512eff71cdaaad571eeb71c111327fc27 /input/autofill/AutofillFramework
parentb271c51d0a116d5cf21d4a25f0bd67d507f4c308 (diff)
downloadandroid-5367691cf1d3f357b70f2488234e5b2442885c14.tar.gz
Moar changes on Autofill samples.
- Renamed HeuristicsService to DebugService. - Removed multi-steps support from MyService (it was broken anyways) - Created a new simple, temporary service to handle multi-step logins. - Added anti-pattern example for login without hints. - Added anti-pattern example for autocomplete without using autofill callbacks. - Added anti-pattern example for multi-step login using activities. - Added edge-case example for custom theme that changes autofilled text. Bug: 114236837 Test: manual verification Change-Id: I7f0f63c090507c247f16eda0075cc20e6dc35139
Diffstat (limited to 'input/autofill/AutofillFramework')
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml7
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/CallbackLessAutoCompleteSignInActivity.java109
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/HintlessSignInActivity.java27
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/PasswordOnlyActivity.java56
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/UsernameOnlyActivity.java53
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java6
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java5
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CustomThemeSignInActivity.java40
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/res/drawable/custom_autofilled_highlight.xml18
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml39
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/res/layout/hintless_login_activity.xml140
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/res/layout/password_only_activity.xml56
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/res/layout/username_only_activity.xml55
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml22
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/res/values/styles.xml4
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml15
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/MyPreferences.java9
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java1
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/DebugService.java319
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java300
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/MultiStepsService.java180
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java2
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/res/xml/debug_service.xml (renamed from input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml)0
23 files changed, 1159 insertions, 304 deletions
diff --git a/input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml
index 624eaee1..59368c6c 100644
--- a/input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml
@@ -53,7 +53,12 @@
<activity android:name="com.example.android.autofill.app.edgecases.MultipleStepsCreditCardActivity" />
<activity android:name="com.example.android.autofill.app.commoncases.RecyclerViewActivity" />
<activity android:name="com.example.android.autofill.app.antipatterns.BadViewStructureCreationSignInActivity" />
-
+ <activity android:name="com.example.android.autofill.app.antipatterns.UsernameOnlyActivity" />
+ <activity android:name="com.example.android.autofill.app.antipatterns.PasswordOnlyActivity" />
+ <activity android:name="com.example.android.autofill.app.antipatterns.HintlessSignInActivity" />
+ <activity android:name="com.example.android.autofill.app.antipatterns.CallbackLessAutoCompleteSignInActivity" />
+ <activity android:name="com.example.android.autofill.app.edgecases.CustomThemeSignInActivity"
+ android:theme="@style/CustomAutofilledHighlightTheme" />
</application>
</manifest>
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/CallbackLessAutoCompleteSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/CallbackLessAutoCompleteSignInActivity.java
new file mode 100644
index 00000000..9f28912f
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/CallbackLessAutoCompleteSignInActivity.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.autofill.app.antipatterns;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+import com.example.android.autofill.app.view.widget.InfoButton;
+
+import static com.example.android.autofill.app.Util.TAG;
+
+public class CallbackLessAutoCompleteSignInActivity extends AppCompatActivity {
+ private AutoCompleteTextView mUsernameAutoCompleteField;
+ private TextView mPasswordField;
+ private TextView mLoginButton;
+ private TextView mClearButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.login_with_autocomplete_activity);
+
+ TextView title = findViewById(R.id.standard_login_header);
+ title.setText(R.string.navigation_button_anti_pattern_callbackless_autocomplete_login_label);
+
+ InfoButton info = findViewById(R.id.imageButton);
+ info.setInfoText(getString(R.string.anti_pattern_callbackless_autocomplete_login_info));
+
+
+ mLoginButton = findViewById(R.id.login);
+ mClearButton = findViewById(R.id.clear);
+ mUsernameAutoCompleteField = findViewById(R.id.usernameField);
+ mPasswordField = findViewById(R.id.passwordField);
+ mLoginButton.setOnClickListener((v) -> login());
+ mClearButton.setOnClickListener((v) -> {
+ AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.cancel();
+ }
+ resetFields();
+ });
+ ArrayAdapter<CharSequence> mockAutocompleteAdapter = ArrayAdapter.createFromResource
+ (this, R.array.mock_autocomplete_sign_in_suggestions,
+ android.R.layout.simple_dropdown_item_1line);
+ mUsernameAutoCompleteField.setAdapter(mockAutocompleteAdapter);
+ mUsernameAutoCompleteField.setThreshold(1);
+
+ // Show it right away
+ mUsernameAutoCompleteField.setOnFocusChangeListener((v, hasFocus) -> {
+ if (hasFocus) {
+ mUsernameAutoCompleteField.showDropDown();
+ }
+ });
+ }
+
+ private void resetFields() {
+ mUsernameAutoCompleteField.setText("");
+ mPasswordField.setText("");
+ }
+
+ /**
+ * Emulates a login action.
+ */
+ private void login() {
+ String username = mUsernameAutoCompleteField.getText().toString();
+ String password = mPasswordField.getText().toString();
+ boolean valid = isValidCredentials(username, password);
+ if (valid) {
+ Intent intent = WelcomeActivity.getStartActivityIntent(CallbackLessAutoCompleteSignInActivity.this);
+ startActivity(intent);
+ finish();
+ } else {
+ Toast.makeText(this, "Authentication failed.", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * Dummy implementation for demo purposes. A real service should use secure mechanisms to
+ * authenticate users.
+ */
+ public boolean isValidCredentials(String username, String password) {
+ return username != null && password != null && username.equals(password);
+ }
+} \ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/HintlessSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/HintlessSignInActivity.java
new file mode 100644
index 00000000..dfb6d094
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/HintlessSignInActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.autofill.app.antipatterns;
+
+import com.example.android.autofill.app.commoncases.StandardSignInActivity;
+import com.example.android.autofill.app.R;
+
+public class HintlessSignInActivity extends StandardSignInActivity {
+
+ @Override
+ protected int getContentView() {
+ return R.layout.hintless_login_activity;
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/PasswordOnlyActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/PasswordOnlyActivity.java
new file mode 100644
index 00000000..44400baf
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/PasswordOnlyActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.autofill.app.antipatterns;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+/**
+ * This activity is the second step in a multi-screen login workflow that uses 2 distinct activities
+ * for username and password, which causes 2 Autofill Save UI to be shown.
+ *
+ * <p>This is a bad pattern anyways&mdash;apps should use Fragments in such scenarios.
+ */
+/*
+ * TODO list
+ * - use ConstraintLayout
+ * - use strings.xml insteaf of hardcoded strings
+ * - add icon with information
+ * - extend AppCompatActivity
+ */
+public final class PasswordOnlyActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.password_only_activity);
+
+ findViewById(R.id.login).setOnClickListener((v) -> login());
+ }
+
+ protected int getContentView() {
+ return R.layout.password_only_activity;
+ }
+
+ void login() {
+ startActivity(new Intent(this, WelcomeActivity.class));
+ finish();
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/UsernameOnlyActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/UsernameOnlyActivity.java
new file mode 100644
index 00000000..dc5dde19
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/antipatterns/UsernameOnlyActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.autofill.app.antipatterns;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import com.example.android.autofill.app.R;
+
+/**
+ * This activity is the first step in a multi-screen login workflow that uses 2 distinct activities
+ * for username and password, which causes 2 Autofill Save UI to be shown.
+ *
+ * <p>This is a bad pattern anyways&mdash;apps should use Fragments in such scenarios.
+ */
+
+/*
+ * TODO list
+ * - use ConstraintLayout
+ * - use strings.xml insteaf of hardcoded strings
+ * - add icon with information
+ * - extend AppCompatActivity
+ */
+public class UsernameOnlyActivity extends Activity {
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ setContentView(R.layout.username_only_activity);
+ findViewById(R.id.next).setOnClickListener((v) -> next());
+ }
+
+ private void next() {
+ startActivity(new Intent(this, PasswordOnlyActivity.class));
+ finish();
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java
index c333bce1..3e3b5e9f 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java
@@ -36,7 +36,7 @@ public class StandardSignInActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.login_activity);
+ setContentView(getContentView());
mUsernameEditText = findViewById(R.id.usernameField);
mPasswordEditText = findViewById(R.id.passwordField);
findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
@@ -57,6 +57,10 @@ public class StandardSignInActivity extends AppCompatActivity {
});
}
+ protected int getContentView() {
+ return R.layout.login_activity;
+ }
+
private void resetFields() {
mUsernameEditText.setText("");
mPasswordEditText.setText("");
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java
index 2baf3351..fc12baa4 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java
@@ -20,6 +20,7 @@ import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.view.autofill.AutofillManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
@@ -134,6 +135,10 @@ abstract class AbstractMultipleStepsActivity extends AppCompatActivity {
mStatus.setText(message.toString());
mContainer.removeAllViews();
mFinished = true;
+ AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.commit();
+ }
updateButtons();
}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CustomThemeSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CustomThemeSignInActivity.java
new file mode 100644
index 00000000..dbb48e91
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CustomThemeSignInActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.autofill.app.edgecases;
+
+import android.os.Bundle;
+import android.widget.TextView;
+
+import com.example.android.autofill.app.commoncases.StandardSignInActivity;
+import com.example.android.autofill.app.view.widget.InfoButton;
+import com.example.android.autofill.app.R;
+
+/**
+ * Same as {@link StandardSignInActivity}, but using a custom theme (defined in the manifest).
+ */
+public class CustomThemeSignInActivity extends StandardSignInActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ TextView title = findViewById(R.id.standard_login_header);
+ title.setText(R.string.navigation_button_custom_theme_login_label);
+
+ InfoButton info = findViewById(R.id.imageButton);
+ info.setInfoText(getString(R.string.custom_theme_login_info));
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/custom_autofilled_highlight.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/custom_autofilled_highlight.xml
new file mode 100644
index 00000000..f65c32a8
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/custom_autofilled_highlight.xml
@@ -0,0 +1,18 @@
+<!--
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#4DFF0000" />
+</shape>
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml
index 88a353d0..83aac1e0 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml
@@ -109,5 +109,44 @@
app:labelText="@string/navigation_button_anti_pattern_bad_view_structure_label"
app:destinationActivityName="com.example.android.autofill.app.antipatterns.BadViewStructureCreationSignInActivity"/>
+ <com.example.android.autofill.app.view.widget.NavigationItem
+ android:id="@+id/multistepSignInAntiPatternButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:imageColor="@android:color/holo_red_dark"
+ app:infoText="@string/anti_pattern_multistep_signin_info"
+ app:itemLogo="@drawable/ic_disabled_black_24dp"
+ app:labelText="@string/navigation_button_anti_pattern_multistep_signin_label"
+ app:destinationActivityName="com.example.android.autofill.app.antipatterns.UsernameOnlyActivity"/>
+
+ <com.example.android.autofill.app.view.widget.NavigationItem
+ android:id="@+id/hintlessSignInAntiPatternButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:imageColor="@android:color/holo_red_dark"
+ app:infoText="@string/anti_pattern_hintless_signin_info"
+ app:itemLogo="@drawable/ic_disabled_black_24dp"
+ app:labelText="@string/navigation_button_anti_pattern_hintless_signin_label"
+ app:destinationActivityName="com.example.android.autofill.app.antipatterns.HintlessSignInActivity"/>
+
+ <com.example.android.autofill.app.view.widget.NavigationItem
+ android:id="@+id/callbacklessSignInAntiPatternButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:imageColor="@android:color/holo_red_dark"
+ app:infoText="@string/anti_pattern_callbackless_autocomplete_login_info"
+ app:itemLogo="@drawable/ic_disabled_black_24dp"
+ app:labelText="@string/navigation_button_anti_pattern_callbackless_autocomplete_login_label"
+ app:destinationActivityName="com.example.android.autofill.app.antipatterns.CallbackLessAutoCompleteSignInActivity"/>
+
+ <com.example.android.autofill.app.view.widget.NavigationItem
+ android:id="@+id/standardLoginWithCustomThemeButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:imageColor="@android:color/holo_blue_dark"
+ app:infoText="@string/custom_theme_login_info"
+ app:itemLogo="@drawable/ic_autocomplete_logo_24dp"
+ app:labelText="@string/navigation_button_custom_theme_login_label"
+ app:destinationActivityName="com.example.android.autofill.app.edgecases.CustomThemeSignInActivity"/>
</LinearLayout>
</ScrollView> \ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/hintless_login_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/hintless_login_activity.xml
new file mode 100644
index 00000000..433ec306
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/hintless_login_activity.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/authLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin">
+
+ <TextView
+ android:id="@+id/standard_login_header"
+ style="@style/TextAppearance.AppCompat.Large"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:gravity="center"
+ android:text="@string/navigation_button_anti_pattern_hintless_signin_label"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/imageButton"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintHorizontal_chainStyle="spread"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <com.example.android.autofill.app.view.widget.InfoButton
+ android:id="@+id/imageButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:background="@drawable/ic_info_black_24dp"
+ app:dialogText="@string/anti_pattern_hintless_signin_info"
+ app:layout_constraintBottom_toBottomOf="@+id/standard_login_header"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toEndOf="@+id/standard_login_header"
+ app:layout_constraintTop_toTopOf="@+id/standard_login_header" />
+
+ <TextView
+ android:id="@+id/usernameLabel"
+ style="@style/TextAppearance.AppCompat.Body1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/spacing_normal"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:layout_marginTop="@dimen/spacing_large"
+ android:labelFor="@+id/obscureFieldId"
+ android:text="@string/username_label"
+ app:layout_constraintEnd_toStartOf="@+id/obscureFieldId"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/standard_login_header" />
+
+ <EditText
+ android:id="@+id/obscureFieldId"
+ android:layout_width="@dimen/text_field_width"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/spacing_normal"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:layout_marginTop="@dimen/spacing_normal"
+ android:inputType="text"
+ app:layout_constraintBottom_toBottomOf="@+id/usernameLabel"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/usernameLabel"
+ app:layout_constraintTop_toTopOf="@+id/usernameLabel" />
+
+ <TextView
+ android:id="@+id/passwordLabel"
+ style="@style/TextAppearance.AppCompat.Body1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/spacing_normal"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:layout_marginTop="@dimen/spacing_large"
+ android:labelFor="@+id/anotherObscureFieldId"
+ android:text="@string/password_label"
+ app:layout_constraintEnd_toStartOf="@+id/anotherObscureFieldId"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/usernameLabel" />
+
+ <EditText
+ android:id="@+id/anotherObscureFieldId"
+ android:layout_width="@dimen/text_field_width"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/spacing_normal"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:layout_marginTop="@dimen/spacing_normal"
+ android:inputType="textPassword"
+ app:layout_constraintBottom_toBottomOf="@+id/passwordLabel"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/passwordLabel"
+ app:layout_constraintTop_toTopOf="@+id/passwordLabel" />
+
+ <TextView
+ android:id="@+id/clear"
+ style="@style/Widget.AppCompat.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:layout_marginTop="@dimen/spacing_normal"
+ android:text="@string/clear_label"
+ android:textColor="@android:color/holo_blue_dark"
+ app:layout_constraintEnd_toStartOf="@+id/login"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/anotherObscureFieldId" />
+
+ <TextView
+ android:id="@+id/login"
+ style="@style/Widget.AppCompat.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/spacing_normal"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:text="@string/login_label"
+ android:textColor="@android:color/holo_blue_dark"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toEndOf="@+id/clear"
+ app:layout_constraintTop_toTopOf="@+id/clear" />
+
+</android.support.constraint.ConstraintLayout>
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/password_only_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/password_only_activity.xml
new file mode 100644
index 00000000..a0fbd1d9
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/password_only_activity.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<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:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/password_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Password" />
+
+ <EditText
+ android:id="@+id/password"
+ android:autofillHints="password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/login"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Login" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/username_only_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/username_only_activity.xml
new file mode 100644
index 00000000..5ce2887e
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/username_only_activity.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<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:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/username_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Username" />
+
+ <EditText
+ android:id="@+id/username"
+ android:autofillHints="username"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/next"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Next" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml b/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml
index fbd8a31d..8b40483f 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml
@@ -34,6 +34,10 @@
<string name="navigation_button_anti_pattern_bad_view_structure_label">Bad View Structure Creation Anti Pattern</string>
<string name="navigation_button_multistep_signin_label">Multi-Step Sign In</string>
<string name="navigation_button_multistep_cc_label">Multi-Step Credit Card Check Out</string>
+ <string name="navigation_button_anti_pattern_multistep_signin_label">Multi-Step Sign In Anti Pattern</string>
+ <string name="navigation_button_anti_pattern_hintless_signin_label">Hintless Sign In Anti Pattern</string>
+ <string name="navigation_button_anti_pattern_callbackless_autocomplete_login_label">Auto Complete without Autofill Integration Anti Pattern</string>
+ <string name="navigation_button_custom_theme_login_label">Sign In Using Custom Theme</string>
<string name="username_label">Username</string>
<string name="password_label">Password</string>
<string name="welcome_text">Success!</string>
@@ -136,6 +140,9 @@
structure is created onStart() instead of onCreate(), which causes autofill to misbehave
when the autofill service requires authentication.
</string>
+ <string name="anti_pattern_hintless_signin_info">This is a sample login page that uses standard EditTexts
+ from the UI toolkit, but without annotating them with android:autofillHints.
+ </string>
<string name="multi_step_signin_info">
<!--TODO-->
@@ -145,6 +152,18 @@
<!--TODO-->
</string>
+ <string name="anti_pattern_multistep_signin_info">
+ <!--TODO-->
+ </string>
+
+ <string name="anti_pattern_callbackless_autocomplete_login_info">
+ <!--TODO-->
+ </string>
+
+ <string name="custom_theme_login_info">
+ <!--TODO-->
+ </string>
+
<string name="partition_credentials">Credentials</string>
<string name="partition_credit_card">Credit Card</string>
@@ -169,8 +188,7 @@
</plurals>
<string-array name="mock_autocomplete_sign_in_suggestions">
- <item>user-1</item>
- <item>user-2</item>
+ <item>app_provided_user</item>
</string-array>
<string-array name="month_array">
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/values/styles.xml b/input/autofill/AutofillFramework/Application/src/main/res/values/styles.xml
index 303a44dc..e7549acc 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/values/styles.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/values/styles.xml
@@ -19,6 +19,10 @@
<!-- Customize your theme here. -->
</style>
+ <style name="CustomAutofilledHighlightTheme" parent="AppTheme">
+ <item name="android:autofilledHighlight">@drawable/custom_autofilled_highlight</item>
+ </style>
+
<style name="CustomDatePickerDialogTheme" parent="android:Theme.Material.Light.Dialog">
<item name="android:datePickerStyle">@style/MyDatePickerStyle</item>
</style>
diff --git a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
index 8c0b7750..9bfff73c 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
+++ b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
@@ -36,12 +36,21 @@
</service>
<service
- android:name=".simple.HeuristicsService"
- android:label="Heuristics Autofill Service"
+ android:name=".simple.DebugService"
+ android:label="Debug Autofill Service"
android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<meta-data
android:name="android.autofill"
- android:resource="@xml/heuristics_service"/>
+ android:resource="@xml/debug_service"/>
+ <intent-filter>
+ <action android:name="android.service.autofill.AutofillService" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name=".simple.MultiStepsService"
+ android:label="Multiple-steps Service"
+ android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/MyPreferences.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/MyPreferences.java
index 8770ef83..6a723f0d 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/MyPreferences.java
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/MyPreferences.java
@@ -29,6 +29,7 @@ public class MyPreferences {
private static final String MASTER_PASSWORD_KEY = "master_password";
private static final String LOGGING_LEVEL = "logging_level";
private static final String DAL_CHECK_REQUIRED = "dal_check_required";
+ private static final String NUMBER_DATASETS = "number_datasets";
private static MyPreferences sInstance;
private final SharedPreferences mPrefs;
@@ -107,4 +108,12 @@ public class MyPreferences {
public void setDalCheckRequired(Util.DalCheckRequirement level) {
mPrefs.edit().putInt(DAL_CHECK_REQUIRED, level.ordinal()).apply();
}
+
+ public int getNumberDatasets(int defaultNumber) {
+ return mPrefs.getInt(NUMBER_DATASETS, defaultNumber);
+ }
+
+ public void setNumberDatasets(int number) {
+ mPrefs.edit().putInt(NUMBER_DATASETS, number).apply();
+ }
}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java
index 1fd87df0..500ceeca 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java
@@ -206,6 +206,7 @@ public class SettingsActivity extends AppCompatActivity {
.setView(numberOfDatasetsPicker)
.setPositiveButton(R.string.settings_ok, (dialog, which) -> {
int numOfDatasets = numberOfDatasetsPicker.getValue();
+ mPreferences.setNumberDatasets(numOfDatasets);
mLocalAutofillDataSource.getFieldTypes(new DataCallback<List<FieldTypeWithHeuristics>>() {
@Override
public void onLoaded(List<FieldTypeWithHeuristics> fieldTypes) {
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/DebugService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/DebugService.java
new file mode 100644
index 00000000..501801e8
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/DebugService.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.autofill.service.simple;
+
+import static com.example.android.autofill.service.simple.BasicService.getLatestAssistStructure;
+import static com.example.android.autofill.service.simple.BasicService.newDatasetPresentation;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.content.Context;
+import android.content.IntentSender;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.SaveRequest;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.example.android.autofill.service.MyAutofillService;
+import com.example.android.autofill.service.settings.MyPreferences;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A basic service that provides autofill data for pretty much any input field, even those not
+ * annotated with autfoill hints.
+ *
+ * <p>The goal of this class is to provide a simple autofill service implementation that can be used
+ * to debug how other apps interact with autofill, it should <strong>not</strong> be used as a
+ * reference for real autofill service implementations because it lacks fundamental security
+ * requirements such as data partitioning and package verification &mdashthese requirements are
+ * fullfilled by {@link MyAutofillService}.
+ */
+public class DebugService extends AutofillService {
+
+ private static final String TAG = "DebugService";
+
+ private boolean mAuthenticateResponses;
+ private boolean mAuthenticateDatasets;
+ private int mNumberDatasets;
+
+ @Override
+ public void onConnected() {
+ super.onConnected();
+
+ // TODO(b/114236837): use its own preferences?
+ MyPreferences pref = MyPreferences.getInstance(getApplicationContext());
+ mAuthenticateResponses = pref.isResponseAuth();
+ mAuthenticateDatasets = pref.isDatasetAuth();
+ mNumberDatasets = pref.getNumberDatasets(4);
+
+ Log.d(TAG, "onConnected(): numberDatasets=" + mNumberDatasets
+ + ", authResponses=" + mAuthenticateResponses
+ + ", authDatasets=" + mAuthenticateDatasets);
+ }
+
+ @Override
+ public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+ FillCallback callback) {
+ Log.d(TAG, "onFillRequest()");
+
+ // Find autofillable fields
+ AssistStructure structure = getLatestAssistStructure(request);
+ ArrayMap<String, AutofillId> fields = getAutofillableFields(structure);
+ Log.d(TAG, "autofillable fields:" + fields);
+
+ if (fields.isEmpty()) {
+ toast("No autofill hints found");
+ callback.onSuccess(null);
+ return;
+ }
+
+ // Create response...
+ FillResponse response;
+ if (mAuthenticateResponses) {
+ int size = fields.size();
+ String[] hints = new String[size];
+ AutofillId[] ids = new AutofillId[size];
+ for (int i = 0; i < size; i++) {
+ hints[i] = fields.keyAt(i);
+ ids[i] = fields.valueAt(i);
+ }
+
+ IntentSender authentication = SimpleAuthActivity.newIntentSenderForResponse(this, hints,
+ ids, mAuthenticateDatasets);
+ RemoteViews presentation = newDatasetPresentation(getPackageName(),
+ "Tap to auth response");
+
+ response = new FillResponse.Builder()
+ .setAuthentication(ids, authentication, presentation).build();
+ } else {
+ response = createResponse(this, fields, mNumberDatasets,mAuthenticateDatasets);
+ }
+
+ // ... and return it
+ callback.onSuccess(response);
+ }
+
+ @Override
+ public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+ Log.d(TAG, "onSaveRequest()");
+ toast("Save not supported");
+ callback.onSuccess();
+ }
+
+ /**
+ * 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 ViewNode} whose {@link #getHint(ViewNode)} metho
+ */
+ @NonNull
+ private ArrayMap<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) {
+ ArrayMap<String, AutofillId> fields = new ArrayMap<>();
+ int nodes = structure.getWindowNodeCount();
+ for (int i = 0; i < nodes; i++) {
+ ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
+ addAutofillableFields(fields, node);
+ }
+ return fields;
+ }
+
+ /**
+ * Adds any autofillable view from the {@link ViewNode} and its descendants to the map.
+ */
+ private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
+ @NonNull ViewNode node) {
+ String hint = getHint(node);
+ if (hint != null) {
+ AutofillId id = node.getAutofillId();
+ if (!fields.containsKey(hint)) {
+ Log.v(TAG, "Setting hint '" + hint + "' on " + id);
+ fields.put(hint, id);
+ } else {
+ Log.v(TAG, "Ignoring hint '" + hint + "' on " + id
+ + " because it was already set");
+ }
+ }
+ int childrenSize = node.getChildCount();
+ for (int i = 0; i < childrenSize; i++) {
+ addAutofillableFields(fields, node.getChildAt(i));
+ }
+ }
+
+ @Nullable
+ protected String getHint(@NonNull ViewNode node) {
+
+ // First try the explicit autofill hints...
+
+ String[] hints = node.getAutofillHints();
+ if (hints != null) {
+ // We're simple, we only care about the first hint
+ return hints[0].toLowerCase();
+ }
+
+ // Then try some rudimentary heuristics based on other node properties
+
+ String viewHint = node.getHint();
+ String hint = inferHint(node, viewHint);
+ if (hint != null) {
+ Log.d(TAG, "Found hint using view hint(" + viewHint + "): " + hint);
+ return hint;
+ } else if (!TextUtils.isEmpty(viewHint)) {
+ Log.v(TAG, "No hint using view hint: " + viewHint);
+ }
+
+ String resourceId = node.getIdEntry();
+ hint = inferHint(node, resourceId);
+ if (hint != null) {
+ Log.d(TAG, "Found hint using resourceId(" + resourceId + "): " + hint);
+ return hint;
+ } else if (!TextUtils.isEmpty(resourceId)) {
+ Log.v(TAG, "No hint using resourceId: " + resourceId);
+ }
+
+ CharSequence text = node.getText();
+ CharSequence className = node.getClassName();
+ if (text != null && className != null && className.toString().contains("EditText")) {
+ hint = inferHint(node, text.toString());
+ if (hint != null) {
+ // NODE: text should not be logged, as it could contain PII
+ Log.d(TAG, "Found hint using text(" + text + "): " + hint);
+ return hint;
+ }
+ } else if (!TextUtils.isEmpty(text)) {
+ // NODE: text should not be logged, as it could contain PII
+ Log.v(TAG, "No hint using text: " + text + " and class " + className);
+ }
+ return null;
+ }
+
+ /**
+ * Uses heuristics to infer an autofill hint from a {@code string}.
+ *
+ * @return standard autofill hint, or {@code null} when it could not be inferred.
+ */
+ @Nullable
+ protected String inferHint(ViewNode node, @Nullable String actualHint) {
+ if (actualHint == null) return null;
+
+ String hint = actualHint.toLowerCase();
+ if (hint.contains("label") || hint.contains("container")) {
+ Log.v(TAG, "Ignoring 'label/container' hint: " + hint);
+ return null;
+ }
+
+ if (hint.contains("password")) return View.AUTOFILL_HINT_PASSWORD;
+ if (hint.contains("username")
+ || (hint.contains("login") && hint.contains("id")))
+ return View.AUTOFILL_HINT_USERNAME;
+ if (hint.contains("email")) return View.AUTOFILL_HINT_EMAIL_ADDRESS;
+ if (hint.contains("name")) return View.AUTOFILL_HINT_NAME;
+ if (hint.contains("phone")) return View.AUTOFILL_HINT_PHONE;
+
+ // When everything else fails, return the full string - this is helpful to help app
+ // developers visualize when autofill is triggered when it shouldn't (for example, in a
+ // chat conversation window), so they can mark the root view of such activities with
+ // android:importantForAutofill=noExcludeDescendants
+ if (node.isEnabled() && node.getAutofillType() != View.AUTOFILL_TYPE_NONE) {
+ Log.v(TAG, "Falling back to " + actualHint);
+ return actualHint;
+ }
+ return null;
+ }
+
+ static FillResponse createResponse(@NonNull Context context,
+ @NonNull ArrayMap<String, AutofillId> fields, int numDatasets,
+ boolean authenticateDatasets) {
+ String packageName = context.getPackageName();
+ FillResponse.Builder response = new FillResponse.Builder();
+ // 1.Add the dynamic datasets
+ for (int i = 1; i <= numDatasets; i++) {
+ Dataset unlockedDataset = newUnlockedDataset(fields, packageName, i);
+ if (authenticateDatasets) {
+ Dataset.Builder lockedDataset = new Dataset.Builder();
+ for (Entry<String, AutofillId> field : fields.entrySet()) {
+ String hint = field.getKey();
+ AutofillId id = field.getValue();
+ String value = i + "-" + hint;
+ IntentSender authentication =
+ SimpleAuthActivity.newIntentSenderForDataset(context, unlockedDataset);
+ RemoteViews presentation = newDatasetPresentation(packageName,
+ "Tap to auth " + value);
+ lockedDataset.setValue(id, null, presentation)
+ .setAuthentication(authentication);
+ }
+ response.addDataset(lockedDataset.build());
+ } else {
+ response.addDataset(unlockedDataset);
+ }
+ }
+
+ // 2.Add save info
+ Collection<AutofillId> ids = fields.values();
+ AutofillId[] requiredIds = new AutofillId[ids.size()];
+ ids.toArray(requiredIds);
+ response.setSaveInfo(
+ // We're simple, so we're generic
+ new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build());
+
+ // 3.Profit!
+ return response.build();
+ }
+
+ static Dataset newUnlockedDataset(@NonNull Map<String, AutofillId> fields,
+ @NonNull String packageName, int i) {
+ Dataset.Builder dataset = new Dataset.Builder();
+ for (Entry<String, AutofillId> field : fields.entrySet()) {
+ String hint = field.getKey();
+ AutofillId id = field.getValue();
+ String value = i + "-" + hint;
+
+ // We're simple - our dataset values are hardcoded as "N-hint" (for example,
+ // "1-username", "2-username") and they're displayed as such, except if they're a
+ // password
+ String displayValue = hint.contains("password") ? "password for #" + i : value;
+ RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
+ dataset.setValue(id, AutofillValue.forText(value), presentation);
+ }
+
+ return dataset.build();
+ }
+
+ /**
+ * Displays a toast with the given message.
+ */
+ private void toast(@NonNull CharSequence message) {
+ Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
index 2cc65729..bddc149d 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
@@ -15,303 +15,11 @@
*/
package com.example.android.autofill.service.simple;
-import static com.example.android.autofill.service.simple.BasicService.getLatestAssistStructure;
-import static com.example.android.autofill.service.simple.BasicService.newDatasetPresentation;
-
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.content.Context;
-import android.content.IntentSender;
-import android.os.CancellationSignal;
-import android.service.autofill.AutofillService;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillRequest;
-import android.service.autofill.FillResponse;
-import android.service.autofill.SaveCallback;
-import android.service.autofill.SaveInfo;
-import android.service.autofill.SaveRequest;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-import android.widget.Toast;
-
-import com.example.android.autofill.service.MyAutofillService;
-import com.example.android.autofill.service.settings.MyPreferences;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Map.Entry;
-
/**
- * A basic service that uses some rudimentary heuristics to identify fields that are not explicitly
- * marked with autofill hints.
- *
- * <p>The goal of this class is to provide a simple autofill service implementation that is easy
- * to understand and extend, but it should <strong>not</strong> be used as-is on real apps because
- * it lacks fundamental security requirements such as data partitioning and package verification
- * &mdashthese requirements are fullfilled by {@link MyAutofillService}.
+ * @deprecated this class was renamed to {@link DebugService}, but it's still in the project because
+ * it's linked from the Autofill guide docs site.
*/
-public class HeuristicsService extends AutofillService {
-
- private static final String TAG = "HeuristicsService";
-
- private boolean mAuthenticateResponses;
- private boolean mAuthenticateDatasets;
- private int mNumberDatasets = 4;
-
- @Override
- public void onConnected() {
- super.onConnected();
-
- // TODO(b/114236837): use its own preferences?
- MyPreferences pref = MyPreferences.getInstance(getApplicationContext());
- mAuthenticateResponses = pref.isResponseAuth();
- mAuthenticateDatasets = pref.isDatasetAuth();
- // TODO(b/114236837): get number dataset from preferences
-
- Log.d(TAG, "onConnected(): numberDatasets=" + mNumberDatasets
- + ", authResponses=" + mAuthenticateResponses
- + ", authDatasets=" + mAuthenticateDatasets);
- }
-
- @Override
- public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
- FillCallback callback) {
- Log.d(TAG, "onFillRequest()");
-
- // Find autofillable fields
- AssistStructure structure = getLatestAssistStructure(request);
- ArrayMap<String, AutofillId> fields = getAutofillableFields(structure);
- Log.d(TAG, "autofillable fields:" + fields);
-
- if (fields.isEmpty()) {
- toast("No autofill hints found");
- callback.onSuccess(null);
- return;
- }
-
- // Create response...
- FillResponse response;
- if (mAuthenticateResponses) {
- int size = fields.size();
- String[] hints = new String[size];
- AutofillId[] ids = new AutofillId[size];
- for (int i = 0; i < size; i++) {
- hints[i] = fields.keyAt(i);
- ids[i] = fields.valueAt(i);
- }
-
- IntentSender authentication = SimpleAuthActivity.newIntentSenderForResponse(this, hints,
- ids, mAuthenticateDatasets);
- RemoteViews presentation = newDatasetPresentation(getPackageName(),
- "Tap to auth response");
-
- response = new FillResponse.Builder()
- .setAuthentication(ids, authentication, presentation).build();
- } else {
- response = createResponse(this, fields, mNumberDatasets,mAuthenticateDatasets);
- }
-
- // ... and return it
- callback.onSuccess(response);
- }
-
- @Override
- public void onSaveRequest(SaveRequest request, SaveCallback callback) {
- Log.d(TAG, "onSaveRequest()");
- toast("Save not supported");
- callback.onSuccess();
- }
-
- /**
- * 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 ViewNode} whose {@link #getHint(ViewNode)} metho
- */
- @NonNull
- private ArrayMap<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) {
- ArrayMap<String, AutofillId> fields = new ArrayMap<>();
- int nodes = structure.getWindowNodeCount();
- for (int i = 0; i < nodes; i++) {
- ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
- addAutofillableFields(fields, node);
- }
- return fields;
- }
-
- /**
- * Adds any autofillable view from the {@link ViewNode} and its descendants to the map.
- */
- private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
- @NonNull ViewNode node) {
- String hint = getHint(node);
- if (hint != null) {
- AutofillId id = node.getAutofillId();
- if (!fields.containsKey(hint)) {
- Log.v(TAG, "Setting hint '" + hint + "' on " + id);
- fields.put(hint, id);
- } else {
- Log.v(TAG, "Ignoring hint '" + hint + "' on " + id
- + " because it was already set");
- }
- }
- int childrenSize = node.getChildCount();
- for (int i = 0; i < childrenSize; i++) {
- addAutofillableFields(fields, node.getChildAt(i));
- }
- }
-
- @Nullable
- protected String getHint(@NonNull ViewNode node) {
-
- // First try the explicit autofill hints...
-
- String[] hints = node.getAutofillHints();
- if (hints != null) {
- // We're simple, we only care about the first hint
- return hints[0].toLowerCase();
- }
-
- // Then try some rudimentary heuristics based on other node properties
-
- String viewHint = node.getHint();
- String hint = inferHint(node, viewHint);
- if (hint != null) {
- Log.d(TAG, "Found hint using view hint(" + viewHint + "): " + hint);
- return hint;
- } else if (!TextUtils.isEmpty(viewHint)) {
- Log.v(TAG, "No hint using view hint: " + viewHint);
- }
-
- String resourceId = node.getIdEntry();
- hint = inferHint(node, resourceId);
- if (hint != null) {
- Log.d(TAG, "Found hint using resourceId(" + resourceId + "): " + hint);
- return hint;
- } else if (!TextUtils.isEmpty(resourceId)) {
- Log.v(TAG, "No hint using resourceId: " + resourceId);
- }
-
- CharSequence text = node.getText();
- CharSequence className = node.getClassName();
- if (text != null && className != null && className.toString().contains("EditText")) {
- hint = inferHint(node, text.toString());
- if (hint != null) {
- // NODE: text should not be logged, as it could contain PII
- Log.d(TAG, "Found hint using text(" + text + "): " + hint);
- return hint;
- }
- } else if (!TextUtils.isEmpty(text)) {
- // NODE: text should not be logged, as it could contain PII
- Log.v(TAG, "No hint using text: " + text + " and class " + className);
- }
- return null;
- }
-
- /**
- * Uses heuristics to infer an autofill hint from a {@code string}.
- *
- * @return standard autofill hint, or {@code null} when it could not be inferred.
- */
- @Nullable
- protected String inferHint(ViewNode node, @Nullable String string) {
- if (string == null) return null;
-
- string = string.toLowerCase();
- if (string.contains("label")) {
- Log.v(TAG, "Ignoring 'label' hint: " + string);
- return null;
- }
- if (string.contains("password")) return View.AUTOFILL_HINT_PASSWORD;
- if (string.contains("username")
- || (string.contains("login") && string.contains("id")))
- return View.AUTOFILL_HINT_USERNAME;
- if (string.contains("email")) return View.AUTOFILL_HINT_EMAIL_ADDRESS;
- if (string.contains("name")) return View.AUTOFILL_HINT_NAME;
- if (string.contains("phone")) return View.AUTOFILL_HINT_PHONE;
-
- // When everything else fails, return the full string - this is helpful to help app
- // developers visualize when autofill is triggered when it shouldn't (for example, in a
- // chat conversation window), so they can mark the root view of such activities with
- // android:importantForAutofill=noExcludeDescendants
- if (node.isEnabled() && node.getAutofillType() != View.AUTOFILL_TYPE_NONE) {
- Log.v(TAG, "Falling back to " + string);
- return string;
- }
- return null;
- }
-
- static FillResponse createResponse(@NonNull Context context,
- @NonNull ArrayMap<String, AutofillId> fields, int numDatasets,
- boolean authenticateDatasets) {
- String packageName = context.getPackageName();
- FillResponse.Builder response = new FillResponse.Builder();
- // 1.Add the dynamic datasets
- for (int i = 1; i <= numDatasets; i++) {
- Dataset unlockedDataset = newUnlockedDataset(fields, packageName, i);
- if (authenticateDatasets) {
- Dataset.Builder lockedDataset = new Dataset.Builder();
- for (Entry<String, AutofillId> field : fields.entrySet()) {
- String hint = field.getKey();
- AutofillId id = field.getValue();
- String value = i + "-" + hint;
- IntentSender authentication =
- SimpleAuthActivity.newIntentSenderForDataset(context, unlockedDataset);
- RemoteViews presentation = newDatasetPresentation(packageName,
- "Tap to auth " + value);
- lockedDataset.setValue(id, null, presentation)
- .setAuthentication(authentication);
- }
- response.addDataset(lockedDataset.build());
- } else {
- response.addDataset(unlockedDataset);
- }
- }
-
- // 2.Add save info
- Collection<AutofillId> ids = fields.values();
- AutofillId[] requiredIds = new AutofillId[ids.size()];
- ids.toArray(requiredIds);
- response.setSaveInfo(
- // We're simple, so we're generic
- new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build());
-
- // 3.Profit!
- return response.build();
- }
-
- static Dataset newUnlockedDataset(@NonNull Map<String, AutofillId> fields,
- @NonNull String packageName, int i) {
- Dataset.Builder dataset = new Dataset.Builder();
- for (Entry<String, AutofillId> field : fields.entrySet()) {
- String hint = field.getKey();
- AutofillId id = field.getValue();
- String value = i + "-" + hint;
-
- // We're simple - our dataset values are hardcoded as "N-hint" (for example,
- // "1-username", "2-username") and they're displayed as such, except if they're a
- // password
- String displayValue = hint.contains("password") ? "password for #" + i : value;
- RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
- dataset.setValue(id, AutofillValue.forText(value), presentation);
- }
-
- return dataset.build();
- }
+@Deprecated
+public final class HeuristicsService {
- /**
- * Displays a toast with the given message.
- */
- private void toast(@NonNull CharSequence message) {
- Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
- }
}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/MultiStepsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/MultiStepsService.java
new file mode 100644
index 00000000..a4bc338b
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/MultiStepsService.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.autofill.service.simple;
+
+import static com.example.android.autofill.service.simple.BasicService.getLatestAssistStructure;
+import static com.example.android.autofill.service.simple.BasicService.newDatasetPresentation;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.content.Context;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.SaveRequest;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.example.android.autofill.service.MyAutofillService;
+import com.example.android.autofill.service.settings.MyPreferences;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A basic service used to demonstrate multi-steps workflows (such as
+ * {@code MultipleStepsSignInActivity} and {@code MultipleStepsCreditCardActivity}) by saving the
+ * save type from previous requests in the client state bundle that's passed along to next requests.
+ *
+ * <p>This class should <strong>not</strong> be used as a reference for real autofill service
+ * implementations because it lacks fundamental security requirements such as data partitioning and
+ * package verification &mdashthese requirements are fullfilled by {@link MyAutofillService}.
+ */
+public class MultiStepsService extends AutofillService {
+
+ private static final String TAG = "MultiStepsService";
+ private static final String SAVE_TYPE_KEY = "saveType";
+
+ @Override
+ public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+ FillCallback callback) {
+ int saveType = SaveInfo.SAVE_DATA_TYPE_GENERIC;
+ Bundle clientState = request.getClientState();
+ if (clientState != null) {
+ saveType = clientState.getInt(SAVE_TYPE_KEY, saveType);
+ }
+ Log.d(TAG, "onFillRequest(): saveType=" + saveType);
+
+ // Find autofillable fields
+ AssistStructure structure = getLatestAssistStructure(request);
+ ArrayMap<String, AutofillId> fields = getAutofillableFields(structure);
+ Log.d(TAG, "autofillable fields:" + fields);
+
+ if (fields.isEmpty()) {
+ toast("No autofill hints found");
+ callback.onSuccess(null);
+ return;
+ }
+
+ Collection<AutofillId> ids = fields.values();
+ AutofillId[] requiredIds = new AutofillId[ids.size()];
+ ids.toArray(requiredIds);
+ for (int i = 0; i < fields.size(); i++) {
+ String hint = fields.keyAt(i);
+ switch (hint) {
+ case View.AUTOFILL_HINT_USERNAME:
+ saveType |= SaveInfo.SAVE_DATA_TYPE_USERNAME;
+ break;
+ case View.AUTOFILL_HINT_EMAIL_ADDRESS:
+ saveType |= SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
+ break;
+ case View.AUTOFILL_HINT_PASSWORD:
+ saveType |= SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+ break;
+ case View.AUTOFILL_HINT_CREDIT_CARD_NUMBER:
+ case View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE:
+ case View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY:
+ case View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH:
+ case View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR:
+ case View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE:
+ saveType |= SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+ break;
+ case View.AUTOFILL_HINT_POSTAL_ADDRESS:
+ case View.AUTOFILL_HINT_POSTAL_CODE:
+ saveType |= SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+ break;
+ default:
+ Log.d(TAG, "Ignoring hint '" + hint + "'");
+ }
+ }
+
+ Log.d(TAG, "new saveType=" + saveType);
+ if (clientState == null) {
+ // Initial request
+ clientState = new Bundle();
+ }
+ // NOTE: to simplify, we're saving just the saveType, but a real service implementation
+ // would have to save the previous values as well, so they can be used later (for example,
+ // it would have to save the username in the first request so it's used to save the
+ // username + password combo in the second request.
+ clientState.putInt(SAVE_TYPE_KEY, saveType);
+
+ // Create response...
+ callback.onSuccess(new FillResponse.Builder()
+ .setClientState(clientState)
+ .setSaveInfo(new SaveInfo.Builder(saveType, requiredIds).build())
+ .build());
+ }
+
+ @Override
+ public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+ Log.d(TAG, "onSaveRequest()");
+ toast("Save not supported");
+ callback.onSuccess();
+ }
+
+ @NonNull
+ private ArrayMap<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) {
+ ArrayMap<String, AutofillId> fields = new ArrayMap<>();
+ int nodes = structure.getWindowNodeCount();
+ for (int i = 0; i < nodes; i++) {
+ ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
+ addAutofillableFields(fields, node);
+ }
+ return fields;
+ }
+
+ private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
+ @NonNull ViewNode node) {
+ String[] hints = node.getAutofillHints();
+ if (hints != null) {
+ // We're simple, we only care about the first hint
+ String hint = hints[0];
+ AutofillId id = node.getAutofillId();
+ if (!fields.containsKey(hint)) {
+ Log.v(TAG, "Setting hint '" + hint + "' on " + id);
+ fields.put(hint, id);
+ } else {
+ Log.v(TAG, "Ignoring hint '" + hint + "' on " + id
+ + " because it was already set");
+ }
+ }
+ int childrenSize = node.getChildCount();
+ for (int i = 0; i < childrenSize; i++) {
+ addAutofillableFields(fields, node.getChildAt(i));
+ }
+ }
+
+ private void toast(@NonNull CharSequence message) {
+ Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java
index 4ff97a77..048b140b 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java
@@ -76,7 +76,7 @@ public class SimpleAuthActivity extends Activity {
fields.put(hints[i], (AutofillId) ids[i]);
}
FillResponse response =
- HeuristicsService.createResponse(this, fields, 1, authenticateDatasets);
+ DebugService.createResponse(this, fields, 1, authenticateDatasets);
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, response);
}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml b/input/autofill/AutofillFramework/afservice/src/main/res/xml/debug_service.xml
index fed1c479..fed1c479 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml
+++ b/input/autofill/AutofillFramework/afservice/src/main/res/xml/debug_service.xml