(this, android.R.layout.simple_spinner_item, years) {
+ @Override
+ public CharSequence[] getAutofillOptions() {
+ return years;
+ }
+ });
+ findViewById(R.id.submit).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ submit();
+ }
+ });
+ findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.cancel();
+ }
+ resetFields();
+ }
+ });
+ }
+
+ private void resetFields() {
+ mCcExpirationDaySpinner.setSelection(0);
+ mCcExpirationMonthSpinner.setSelection(0);
+ mCcExpirationYearSpinner.setSelection(0);
+ mCcCardNumber.setText("");
+ mCcSecurityCode.setText("");
+ }
+
+ /**
+ * Launches new Activity and finishes, triggering an autofill save request if the user entered
+ * any new data.
+ */
+ private void submit() {
+ Intent intent = WelcomeActivity.getStartActivityIntent(CreditCardSpinnersActivity.this);
+ startActivity(intent);
+ finish();
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/EmailComposeActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/EmailComposeActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..94a001942047779a0b4df9097280b4139f594658
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/EmailComposeActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 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.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+public class EmailComposeActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.email_compose_activity);
+ findViewById(R.id.sendButton).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(WelcomeActivity.getStartActivityIntent(EmailComposeActivity.this));
+ finish();
+ }
+ });
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/StandardAutoCompleteSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardAutoCompleteSignInActivity.java
similarity index 89%
rename from input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/StandardAutoCompleteSignInActivity.java
rename to input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardAutoCompleteSignInActivity.java
index d5ed88f0230db8948351d8d223c3acf5dbe45896..089c6b2d597789affe7086e7983fad25b7bdb308 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/StandardAutoCompleteSignInActivity.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardAutoCompleteSignInActivity.java
@@ -13,39 +13,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.example.android.autofillframework.app;
+package com.example.android.autofill.app.commoncases;
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.Button;
import android.widget.TextView;
import android.widget.Toast;
-import com.example.android.autofillframework.R;
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
-import static com.example.android.autofillframework.CommonUtil.TAG;
+import static com.example.android.autofill.app.Util.TAG;
public class StandardAutoCompleteSignInActivity extends AppCompatActivity {
private AutoCompleteTextView mUsernameAutoCompleteField;
private TextView mPasswordField;
- private Button mLoginButton;
- private Button mClearButton;
+ private TextView mLoginButton;
+ private TextView mClearButton;
private boolean mAutofillReceived = false;
private AutofillManager.AutofillCallback mAutofillCallback;
private AutofillManager mAutofillManager;
- public static Intent getStartActivityIntent(Context context) {
- Intent intent = new Intent(context, StandardAutoCompleteSignInActivity.class);
- return intent;
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -65,6 +61,10 @@ public class StandardAutoCompleteSignInActivity extends AppCompatActivity {
mClearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
+ AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.cancel();
+ }
resetFields();
}
});
@@ -119,8 +119,7 @@ public class StandardAutoCompleteSignInActivity extends AppCompatActivity {
private class MyAutofillCallback extends AutofillManager.AutofillCallback {
@Override
- public void onAutofillEvent(View view, int event) {
- super.onAutofillEvent(view, event);
+ public void onAutofillEvent(@NonNull View view, int event) {
if (view instanceof AutoCompleteTextView) {
switch (event) {
case AutofillManager.AutofillCallback.EVENT_INPUT_UNAVAILABLE:
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/StandardSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java
similarity index 87%
rename from input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/StandardSignInActivity.java
rename to input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java
index 9038e3da08c386e3286da8fb22d922cc18f08470..c333bce1f51ddc978b25a7cd3e21223d4dba6c68 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/StandardSignInActivity.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/StandardSignInActivity.java
@@ -13,28 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.example.android.autofillframework.app;
+package com.example.android.autofill.app.commoncases;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
+import android.view.autofill.AutofillManager;
import android.widget.EditText;
import android.widget.Toast;
-import com.example.android.autofillframework.R;
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
public class StandardSignInActivity extends AppCompatActivity {
private EditText mUsernameEditText;
private EditText mPasswordEditText;
- public static Intent getStartActivityIntent(Context context) {
- Intent intent = new Intent(context, StandardSignInActivity.class);
- return intent;
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -51,6 +48,10 @@ public class StandardSignInActivity extends AppCompatActivity {
findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
+ AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.cancel();
+ }
resetFields();
}
});
@@ -82,6 +83,6 @@ public class StandardSignInActivity extends AppCompatActivity {
* authenticate users.
*/
public boolean isValidCredentials(String username, String password) {
- return username != null && password != null && username.equals(password);
+ return username != null && password != null && username.equalsIgnoreCase(password);
}
-}
\ No newline at end of file
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/VirtualSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/VirtualSignInActivity.java
similarity index 66%
rename from input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/VirtualSignInActivity.java
rename to input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/VirtualSignInActivity.java
index 3d23e3f805ffee6f5443db9af9f7e304a5d6671c..baafbf75b28eb4f793451c7dd1fcb659f83d8be2 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/VirtualSignInActivity.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/VirtualSignInActivity.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.example.android.autofillframework.app;
+package com.example.android.autofill.app.commoncases;
import android.content.Context;
import android.content.Intent;
@@ -23,18 +23,19 @@ import android.view.View;
import android.view.autofill.AutofillManager;
import android.widget.Toast;
-import com.example.android.autofillframework.R;
-
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+import com.example.android.autofill.app.view.autofillable.CustomVirtualView;
+/**
+ * Activity that uses a virtual views for Username/Password text fields.
+ */
public class VirtualSignInActivity extends AppCompatActivity {
private CustomVirtualView mCustomVirtualView;
private AutofillManager mAutofillManager;
-
- public static Intent getStartActivityIntent(Context context) {
- Intent intent = new Intent(context, VirtualSignInActivity.class);
- return intent;
- }
+ private CustomVirtualView.Line mUsernameLine;
+ private CustomVirtualView.Line mPasswordLine;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -43,6 +44,16 @@ public class VirtualSignInActivity extends AppCompatActivity {
setContentView(R.layout.virtual_login_activity);
mCustomVirtualView = (CustomVirtualView) findViewById(R.id.custom_view);
+
+ CustomVirtualView.Partition credentialsPartition =
+ mCustomVirtualView.addPartition(getString(R.string.partition_credentials));
+ mUsernameLine = credentialsPartition.addLine("username", View.AUTOFILL_TYPE_TEXT,
+ getString(R.string.username_label),
+ " ", false, View.AUTOFILL_HINT_USERNAME);
+ mPasswordLine = credentialsPartition.addLine("password", View.AUTOFILL_TYPE_TEXT,
+ getString(R.string.password_label),
+ " ", true, View.AUTOFILL_HINT_PASSWORD);
+
findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -53,31 +64,24 @@ public class VirtualSignInActivity extends AppCompatActivity {
@Override
public void onClick(View view) {
resetFields();
+ mAutofillManager.cancel();
}
});
mAutofillManager = getSystemService(AutofillManager.class);
}
- @Override
- protected void onResume() {
- super.onResume();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- }
-
private void resetFields() {
- mCustomVirtualView.resetFields();
+ mUsernameLine.reset();
+ mPasswordLine.reset();
+ mCustomVirtualView.postInvalidate();
}
/**
* Emulates a login action.
*/
private void login() {
- String username = mCustomVirtualView.getUsernameText().toString();
- String password = mCustomVirtualView.getPasswordText().toString();
+ String username = mUsernameLine.getText().toString();
+ String password = mPasswordLine.getText().toString();
boolean valid = isValidCredentials(username, password);
if (valid) {
Intent intent = WelcomeActivity.getStartActivityIntent(VirtualSignInActivity.this);
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/WebViewSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/WebViewSignInActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..9bd09f79fae6833432432e1355982e59116594d3
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/commoncases/WebViewSignInActivity.java
@@ -0,0 +1,57 @@
+/*
+* Copyright (C) 2017 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.commoncases;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.example.android.autofill.app.R;
+
+import static com.example.android.autofill.app.Util.DEBUG;
+import static com.example.android.autofill.app.Util.TAG;
+
+public class WebViewSignInActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.login_webview_activity);
+
+ WebView webView = findViewById(R.id.webview);
+ WebSettings webSettings = webView.getSettings();
+ webView.setWebViewClient(new WebViewClient());
+ webSettings.setJavaScriptEnabled(true);
+
+ String url = getIntent().getStringExtra("url");
+ if (url == null) {
+ url = "file:///android_res/raw/sample_form.html";
+ }
+ if (DEBUG) Log.d(TAG, "Clearing WebView data");
+ webView.clearHistory();
+ webView.clearFormData();
+ webView.clearCache(true);
+ Log.i(TAG, "Loading URL " + url);
+ webView.loadUrl(url);
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000000000000000000000000000000000..2baf3351e9501a13c65e619cd6fe06365dc1b163
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/AbstractMultipleStepsActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.example.android.autofill.app.R;
+
+import java.util.Map;
+
+import static com.example.android.autofill.app.Util.DEBUG;
+import static com.example.android.autofill.app.Util.TAG;
+
+/**
+ * Activity that emulates a multiple-steps wizard activity, where each step shows just one
+ * label and input.
+ *
+ *
Its's useful to verify how an autofill service handles account creation that takes multiple
+ * steps.
+ */
+
+/*
+ * TODO list
+ * - use ConstraintLayout
+ * - use Fragments instead of replacing views directly
+ * - use custom view and/or layout xml for mSteps
+ */
+abstract class AbstractMultipleStepsActivity extends AppCompatActivity {
+
+ private TextView mStatus;
+ private ViewGroup mContainer;
+
+ private Button mPrev;
+ private Button mNext;
+ private Button mFinish;
+
+ private int mCurrentStep;
+ private boolean mFinished;
+
+ private LinearLayout[] mSteps;
+
+ /**
+ * Gets the mapping from resource id to autofill hints.
+ */
+ protected abstract Map getStepsMap();
+
+ @Override
+ protected final void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.multiple_steps_activity);
+
+ mStatus = findViewById(R.id.status);
+ mContainer = findViewById(R.id.container);
+ mPrev = findViewById(R.id.prev);
+ mNext = findViewById(R.id.next);
+ mFinish = findViewById(R.id.finish);
+
+ View.OnClickListener onClickListener = (v) -> {
+ if (v == mPrev) {
+ showStep(mCurrentStep - 1);
+ } else if (v == mNext) {
+ showStep(mCurrentStep + 1);
+ } else {
+ finishIt();
+ }
+ };
+ mPrev.setOnClickListener(onClickListener);
+ mNext.setOnClickListener(onClickListener);
+ mFinish.setOnClickListener(onClickListener);
+
+ Map stepsMap = getStepsMap();
+ if (DEBUG) debug("onCreate(): steps=%s", stepsMap);
+ initializeSteps(stepsMap);
+
+ showStep(0);
+ }
+
+ private void showStep(int i) {
+ if (mFinished || i < 0 || i >= mSteps.length) {
+ warn("Invalid step: %d (finished=%s, range=[%d,%d])",
+ mFinished, i, 0, mSteps.length - 1);
+ return;
+ }
+ View step = mSteps[i];
+ mStatus.setText(getString(R.string.message_showing_step, i));
+ if (DEBUG) debug("Showing step %d", i);
+ if (mContainer.getChildCount() > 0) {
+ mContainer.removeViewAt(0);
+ }
+ mContainer.addView(step);
+ mCurrentStep = i;
+
+ mPrev.setEnabled(mCurrentStep != 0);
+ mNext.setEnabled(mCurrentStep != mSteps.length - 1);
+ }
+
+ private void updateButtons() {
+ mPrev.setEnabled(!mFinished && mCurrentStep != 0);
+ mNext.setEnabled(!mFinished && mCurrentStep != mSteps.length - 1);
+ mFinish.setEnabled(!mFinished);
+ }
+
+ private void finishIt() {
+ StringBuilder message = new StringBuilder(getString(R.string.message_finished))
+ .append("\n\n");
+ for (int i = 0; i < mSteps.length; i++) {
+ TextView label = (TextView) mSteps[i].getChildAt(0);
+ EditText input = (EditText) mSteps[i].getChildAt(1);
+ message.append(getString(R.string.message_step_description, label.getText(), input.getText()))
+ .append('\n');
+ }
+ mStatus.setText(message.toString());
+ mContainer.removeAllViews();
+ mFinished = true;
+ updateButtons();
+ }
+
+ private void initializeSteps(Map stepsMap) {
+ mSteps = new LinearLayout[stepsMap.size()];
+ int i = 0;
+ for (Map.Entry entry : stepsMap.entrySet()) {
+ int labelId = entry.getKey();
+ String autofillHints = entry.getValue();
+ if (DEBUG) debug("step %d: %s->%s", i, getString(labelId), autofillHints);
+ mSteps[i++] = newStep(labelId, autofillHints);
+ }
+ }
+
+ private LinearLayout newStep(int labelId, String autofillHints) {
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.HORIZONTAL);
+
+ TextView label = new TextView(this);
+ label.setText(labelId);
+ layout.addView(label);
+
+ EditText input = new EditText(this);
+ input.setAutofillHints(autofillHints);
+ input.setWidth(500); // TODO: proper size
+ layout.addView(input);
+
+ return layout;
+ }
+
+ protected void debug(String fmt, Object... args) {
+ Log.d(TAG, getLocalClassName() + "." + String.format(fmt, args));
+ }
+
+ protected void warn(String fmt, Object... args) {
+ Log.w(TAG, getLocalClassName() + "." + String.format(fmt, args));
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e68a012af1ab8deb02b11af543c8ede3f464485
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+public class CreditCardActivity extends AppCompatActivity {
+
+ private EditText mCcExpDayView;
+ private EditText mCcExpMonthView;
+ private EditText mCcExpYearView;
+ private EditText mCcNumber;
+ private EditText mCcSecurityCode;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.credit_card_activity);
+ mCcExpDayView = findViewById(R.id.expirationDay);
+ mCcExpMonthView = findViewById(R.id.expirationMonth);
+ mCcExpYearView = findViewById(R.id.expirationYear);
+ mCcNumber = findViewById(R.id.creditCardNumberField);
+ mCcSecurityCode = findViewById(R.id.creditCardSecurityCode);
+ findViewById(R.id.submitButton).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ submit();
+ }
+ });
+ findViewById(R.id.clearButton).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.cancel();
+ }
+ resetFields();
+ }
+ });
+ }
+
+ private void resetFields() {
+ mCcExpDayView.setText("");
+ mCcExpMonthView.setText("");
+ mCcExpYearView.setText("");
+ mCcNumber.setText("");
+ mCcSecurityCode.setText("");
+ }
+
+ /**
+ * Launches new Activity and finishes, triggering an autofill save request if the user entered
+ * any new data.
+ */
+ private void submit() {
+ Intent intent = WelcomeActivity.getStartActivityIntent(this);
+ startActivity(intent);
+ finish();
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardAntiPatternActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardAntiPatternActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..edffcc054bd130d575ef5b9168952dba78828077
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/CreditCardAntiPatternActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.WelcomeActivity;
+
+public class CreditCardAntiPatternActivity extends AppCompatActivity {
+
+ private EditText mCcExpDateView;
+ private EditText mCcExpNumber;
+ private EditText mCcSecurityCode;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.credit_card_anti_pattern_activity);
+ mCcExpDateView = findViewById(R.id.creditCardExpirationView);
+ mCcExpNumber = findViewById(R.id.creditCardNumberField);
+ mCcSecurityCode = findViewById(R.id.creditCardSecurityCode);
+ findViewById(R.id.submitButton).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ submit();
+ }
+ });
+ findViewById(R.id.clearButton).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.cancel();
+ }
+ resetFields();
+ }
+ });
+ }
+
+ private void resetFields() {
+ mCcExpDateView.setText("");
+ mCcExpNumber.setText("");
+ mCcSecurityCode.setText("");
+ }
+
+ /**
+ * Launches new Activity and finishes, triggering an autofill save request if the user entered
+ * any new data.
+ */
+ private void submit() {
+ Intent intent = WelcomeActivity.getStartActivityIntent(this);
+ startActivity(intent);
+ finish();
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/EdgeCasesFragment.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/EdgeCasesFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..917be103dd1c4f7f300d34ffa7ad9b45ed839f19
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/EdgeCasesFragment.java
@@ -0,0 +1,40 @@
+/*
+* Copyright (C) 2017 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.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.example.android.autofill.app.BaseMainFragment;
+import com.example.android.autofill.app.R;
+
+public class EdgeCasesFragment extends BaseMainFragment {
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_edge_cases, container, false);
+ }
+
+ @Override
+ public int getPageTitleResId() {
+ return R.string.edge_cases_page_title;
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e09b45b0bbf281b2cc9f9a1b56c3140f46f5eeb
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 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.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.Util;
+import com.example.android.autofill.app.view.autofillable.CustomVirtualView;
+import com.example.android.autofill.app.view.autofillable.ScrollableCustomVirtualView;
+
+/**
+ * Activity used to demonstrated safe partitioning of data.
+ *
+ *
It has multiple partitions, but only accepts autofill on each partition at time.
+ */
+/*
+ * TODO list
+ *
+ * - Fix top margin.
+ * - Use a combo box to select if credit card expiration date is expressed as date or text.
+ * - Use a dedicated TextView (instead of Toast) for error messages.
+ * - Use wrap_context to CustomView container.
+ * - Use different background color (or borders) for each partition.
+ * - Add more partitions (like address) - should match same partitions from service.
+ * - Add more hints (like w3c ones) - should match same hints from service.
+ */
+public class MultiplePartitionsActivity extends AppCompatActivity {
+
+ private ScrollableCustomVirtualView mCustomVirtualView;
+ private AutofillManager mAutofillManager;
+ private CustomVirtualView.Partition mCredentialsPartition;
+ private CustomVirtualView.Partition mCcPartition;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.multiple_partitions_activity);
+ mCustomVirtualView = findViewById(R.id.custom_view);
+ mCredentialsPartition =
+ mCustomVirtualView.addPartition(getString(R.string.partition_credentials));
+ mCredentialsPartition.addLine("username", View.AUTOFILL_TYPE_TEXT,
+ getString(R.string.username_label),
+ " ", false, View.AUTOFILL_HINT_USERNAME);
+ mCredentialsPartition.addLine("password", View.AUTOFILL_TYPE_TEXT,
+ getString(R.string.password_label),
+ " ", true, View.AUTOFILL_HINT_PASSWORD);
+ int ccExpirationType = View.AUTOFILL_TYPE_DATE;
+ // TODO: add a checkbox to switch between text / date instead
+ Intent intent = getIntent();
+ if (intent != null) {
+ int newType = intent.getIntExtra("dateType", -1);
+ if (newType != -1) {
+ ccExpirationType = newType;
+ String typeMessage = getString(R.string.message_credit_card_expiration_type,
+ Util.getAutofillTypeAsString(ccExpirationType));
+ // TODO: display type in a header or proper status widget
+ Toast.makeText(getApplicationContext(), typeMessage, Toast.LENGTH_LONG).show();
+ }
+ }
+ mCcPartition = mCustomVirtualView.addPartition(getString(R.string.partition_credit_card));
+ mCcPartition.addLine("ccNumber", View.AUTOFILL_TYPE_TEXT,
+ getString(R.string.credit_card_number_label),
+ " ", true, View.AUTOFILL_HINT_CREDIT_CARD_NUMBER);
+ mCcPartition.addLine("ccDay", View.AUTOFILL_TYPE_TEXT,
+ getString(R.string.credit_card_expiration_day_label),
+ " ", true, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY);
+ mCcPartition.addLine("ccMonth", ccExpirationType,
+ getString(R.string.credit_card_expiration_month_label),
+ " ", true, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH);
+ mCcPartition.addLine("ccYear", View.AUTOFILL_TYPE_TEXT,
+ getString(R.string.credit_card_expiration_year_label),
+ " ", true, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR);
+ mCcPartition.addLine("ccDate", ccExpirationType,
+ getString(R.string.credit_card_expiration_date_label),
+ " ", true, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE);
+ mCcPartition.addLine("ccSecurityCode", View.AUTOFILL_TYPE_TEXT,
+ getString(R.string.credit_card_security_code_label),
+ " ", true, View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE);
+ mAutofillManager = getSystemService(AutofillManager.class);
+ findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ resetFields();
+ mCustomVirtualView.resetPositions();
+ mAutofillManager.cancel();
+ }
+ });
+ }
+
+ private void resetFields() {
+ mCredentialsPartition.reset();
+ mCcPartition.reset();
+ mCustomVirtualView.postInvalidate();
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsCreditCardActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsCreditCardActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..804b77f227fdc95d2a9370c87b5d629d1c1b81f2
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsCreditCardActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.view.View;
+
+import com.example.android.autofill.app.R;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class MultipleStepsCreditCardActivity extends AbstractMultipleStepsActivity {
+
+ @Override
+ protected Map getStepsMap() {
+ LinkedHashMap steps = new LinkedHashMap<>(4);
+ steps.put(R.string.credit_card_number_label,
+ View.AUTOFILL_HINT_CREDIT_CARD_NUMBER);
+ steps.put(R.string.credit_card_expiration_month_label,
+ View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH);
+ steps.put(R.string.credit_card_expiration_year_label,
+ View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR);
+ steps.put(R.string.credit_card_security_code_abbrev_label,
+ View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE);
+ return ImmutableMap.copyOf(steps);
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsSignInActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec45a9c01c46c5b7c86bb6f39b7904002c3335ff
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultipleStepsSignInActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.view.View;
+
+import com.example.android.autofill.app.R;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class MultipleStepsSignInActivity extends AbstractMultipleStepsActivity {
+
+ @Override
+ protected Map getStepsMap() {
+ LinkedHashMap steps = new LinkedHashMap<>(2);
+ steps.put(R.string.username_label, View.AUTOFILL_HINT_USERNAME);
+ steps.put(R.string.password_label, View.AUTOFILL_HINT_PASSWORD);
+ return ImmutableMap.copyOf(steps);
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDateCompoundView.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDateCompoundView.java
new file mode 100644
index 0000000000000000000000000000000000000000..1cee75a94a6466edf5cc540601daeaef1dc0005c
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDateCompoundView.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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.view.autofillable;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.Spinner;
+
+import com.example.android.autofill.app.R;
+
+import java.util.Calendar;
+
+import static com.example.android.autofill.app.Util.TAG;
+
+/**
+ * A custom view that represents a {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE} using
+ * 2 {@link Spinner spinners} to represent the credit card expiration month and year.
+ */
+public class CreditCardExpirationDateCompoundView extends FrameLayout {
+
+ private static final int CC_EXP_YEARS_COUNT = 5;
+
+ private final String[] mYears = new String[CC_EXP_YEARS_COUNT];
+
+ private Spinner mCcExpMonthSpinner;
+ private Spinner mCcExpYearSpinner;
+
+ public CreditCardExpirationDateCompoundView(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public CreditCardExpirationDateCompoundView(@NonNull Context context,
+ @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CreditCardExpirationDateCompoundView(@NonNull Context context,
+ @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CreditCardExpirationDateCompoundView(@NonNull final Context context,
+ @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ View rootView = LayoutInflater.from(context).inflate(R.layout.cc_exp_date, this);
+ mCcExpMonthSpinner = rootView.findViewById(R.id.ccExpMonth);
+ mCcExpYearSpinner = rootView.findViewById(R.id.ccExpYear);
+ setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
+ ArrayAdapter monthAdapter = ArrayAdapter.createFromResource
+ (context, R.array.month_array, android.R.layout.simple_spinner_item);
+ monthAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mCcExpMonthSpinner.setAdapter(monthAdapter);
+ int year = Calendar.getInstance().get(Calendar.YEAR);
+ for (int i = 0; i < mYears.length; i++) {
+ mYears[i] = Integer.toString(year + i);
+ }
+ mCcExpYearSpinner.setAdapter(new ArrayAdapter<>(context,
+ android.R.layout.simple_spinner_item, mYears));
+ AdapterView.OnItemSelectedListener onItemSelectedListener =
+ new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ context.getSystemService(AutofillManager.class)
+ .notifyValueChanged(CreditCardExpirationDateCompoundView.this);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ }
+ };
+ mCcExpMonthSpinner.setOnItemSelectedListener(onItemSelectedListener);
+ mCcExpYearSpinner.setOnItemSelectedListener(onItemSelectedListener);
+ }
+
+ @Override
+ public AutofillValue getAutofillValue() {
+ Calendar calendar = Calendar.getInstance();
+ // Set hours, minutes, seconds, and millis to 0 to ensure getAutofillValue() == the value
+ // set by autofill(). Without this line, the view will not turn yellow when updated.
+ calendar.clear();
+ int year = Integer.parseInt(mCcExpYearSpinner.getSelectedItem().toString());
+ int month = mCcExpMonthSpinner.getSelectedItemPosition();
+ calendar.set(Calendar.YEAR, year);
+ calendar.set(Calendar.MONTH, month);
+ long unixTime = calendar.getTimeInMillis();
+ return AutofillValue.forDate(unixTime);
+ }
+
+ @Override
+ public void autofill(AutofillValue value) {
+ if (!value.isDate()) {
+ Log.w(TAG, "Ignoring autofill() because service sent a non-date value:" + value);
+ return;
+ }
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(value.getDateValue());
+ int month = calendar.get(Calendar.MONTH);
+ int year = calendar.get(Calendar.YEAR);
+ mCcExpMonthSpinner.setSelection(month);
+ mCcExpYearSpinner.setSelection(year - Integer.parseInt(mYears[0]));
+ }
+
+ @Override
+ public int getAutofillType() {
+ return AUTOFILL_TYPE_DATE;
+ }
+
+ public void reset() {
+ mCcExpMonthSpinner.setSelection(0);
+ mCcExpYearSpinner.setSelection(0);
+ }
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDatePickerView.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDatePickerView.java
new file mode 100644
index 0000000000000000000000000000000000000000..870825a2643dee33afa8e325014e9b322e2881f3
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CreditCardExpirationDatePickerView.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 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.view.autofillable;
+
+import android.app.DatePickerDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.widget.AppCompatEditText;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillValue;
+import android.widget.DatePicker;
+
+import com.example.android.autofill.app.R;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import static com.example.android.autofill.app.Util.DEBUG;
+import static com.example.android.autofill.app.Util.TAG;
+
+/**
+ * A custom view that represents a {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE} using
+ * a non-editable {@link EditText} that triggers a {@link DatePickerDialog} to represent the
+ * credit card expiration month and year.
+ */
+public class CreditCardExpirationDatePickerView extends AppCompatEditText {
+
+ private static final int CC_EXP_YEARS_COUNT = 5;
+
+ /**
+ * Calendar instance used for month / year calculations. Should be reset before each use.
+ */
+ private final Calendar mTempCalendar;
+
+ private int mMonth;
+ private int mYear;
+
+ public CreditCardExpirationDatePickerView(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public CreditCardExpirationDatePickerView(@NonNull Context context,
+ @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CreditCardExpirationDatePickerView(@NonNull Context context,
+ @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ // Use the current date as the initial date in the picker.
+ mTempCalendar = Calendar.getInstance();
+ mYear = mTempCalendar.get(Calendar.YEAR);
+ mMonth = mTempCalendar.get(Calendar.MONTH);
+ }
+
+ /**
+ * Gets a temporary calendar set with the View's year and month.
+ */
+ private Calendar getCalendar() {
+ mTempCalendar.clear();
+ mTempCalendar.set(Calendar.YEAR, mYear);
+ mTempCalendar.set(Calendar.MONTH, mMonth);
+ mTempCalendar.set(Calendar.DATE, 1);
+ return mTempCalendar;
+ }
+
+ @Override
+ public AutofillValue getAutofillValue() {
+ Calendar c = getCalendar();
+ AutofillValue value = AutofillValue.forDate(c.getTimeInMillis());
+ if (DEBUG) Log.d(TAG, "getAutofillValue(): " + value);
+ return value;
+ }
+
+ @Override
+ public void autofill(AutofillValue value) {
+ if (value == null || !value.isDate()) {
+ Log.w(TAG, "autofill(): invalid value " + value);
+ return;
+ }
+ long time = value.getDateValue();
+ mTempCalendar.setTimeInMillis(time);
+ int year = mTempCalendar.get(Calendar.YEAR);
+ int month = mTempCalendar.get(Calendar.MONTH);
+ if (DEBUG) Log.d(TAG, "autofill(" + value + "): " + month + "/" + year);
+ setDate(year, month);
+ }
+
+ private void setDate(int year, int month) {
+ mYear = year;
+ mMonth = month;
+ Date selectedDate = new Date(getCalendar().getTimeInMillis());
+ String dateString = DateFormat.getDateFormat(getContext()).format(selectedDate);
+ setText(dateString);
+ }
+
+ @Override
+ public int getAutofillType() {
+ return AUTOFILL_TYPE_DATE;
+ }
+
+ public void reset() {
+ mTempCalendar.setTimeInMillis(System.currentTimeMillis());
+ setDate(mTempCalendar.get(Calendar.YEAR), mTempCalendar.get(Calendar.MONTH));
+ }
+
+ public void showDatePickerDialog(FragmentManager fragmentManager) {
+ DatePickerFragment newFragment = new DatePickerFragment();
+ newFragment.mParent = this;
+ newFragment.show(fragmentManager, "datePicker");
+ }
+
+ public static class DatePickerFragment extends DialogFragment
+ implements DatePickerDialog.OnDateSetListener {
+
+ private CreditCardExpirationDatePickerView mParent;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ DatePickerDialog dialog = new DatePickerDialog(getActivity(),
+ R.style.CustomDatePickerDialogTheme, this, mParent.mYear, mParent.mMonth, 1);
+
+ DatePicker datePicker = dialog.getDatePicker();
+
+ // Limit range.
+ Calendar c = mParent.getCalendar();
+ datePicker.setMinDate(c.getTimeInMillis());
+ c.set(Calendar.YEAR, mParent.mYear + CC_EXP_YEARS_COUNT - 1);
+ datePicker.setMaxDate(c.getTimeInMillis());
+
+ // Remove day.
+ datePicker.findViewById(getResources().getIdentifier("day", "id", "android"))
+ .setVisibility(View.GONE);
+ return dialog;
+ }
+
+ @Override
+ public void onDateSet(DatePicker view, int year, int month, int day) {
+ mParent.setDate(year, month);
+ }
+ }
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualView.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualView.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c6e1254e7b08e18fbbfa32af9d8c86ba004835e
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/CustomVirtualView.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2017 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.view.autofillable;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.autofill.app.R;
+import com.example.android.autofill.app.Util;
+import com.google.common.base.Preconditions;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+
+import static com.example.android.autofill.app.Util.bundleToString;
+
+/**
+ * A custom View with a virtual structure for fields supporting {@link View#getAutofillHints()}
+ */
+public class CustomVirtualView extends View {
+
+ protected static final boolean DEBUG = true;
+ protected static final boolean VERBOSE = false;
+
+ /**
+ * When set, it notifies AutofillManager of focus change as the view scrolls, so the
+ * autofill UI is continually drawn.
+ *
+ *
This is janky and incompatible with the way the autofill UI works on native views, but
+ * it's a cool experiment!
+ */
+ private static final boolean DRAW_AUTOFILL_UI_AFTER_SCROLL = false;
+
+ private static final String TAG = "CustomView";
+ private static final int DEFAULT_TEXT_HEIGHT_DP = 34;
+ private static final int VERTICAL_GAP = 10;
+ private static final int UNFOCUSED_COLOR = Color.BLACK;
+ private static final int FOCUSED_COLOR = Color.RED;
+ private static int sNextId;
+ protected final AutofillManager mAutofillManager;
+ private final ArrayList mVirtualViewGroups = new ArrayList<>();
+ private final SparseArray- mVirtualViews = new SparseArray<>();
+ private final SparseArray
mPartitionsByAutofillId = new SparseArray<>();
+ private final ArrayMap mPartitionsByName = new ArrayMap<>();
+ protected Line mFocusedLine;
+ protected int mTopMargin;
+ protected int mLeftMargin;
+ private Paint mTextPaint;
+ private int mTextHeight;
+ private int mLineLength;
+
+ public CustomVirtualView(Context context) {
+ this(context, null);
+ }
+
+ public CustomVirtualView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CustomVirtualView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CustomVirtualView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mAutofillManager = context.getSystemService(AutofillManager.class);
+ mTextPaint = new Paint();
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomVirtualView,
+ defStyleAttr, defStyleRes);
+ int defaultHeight =
+ (int) (DEFAULT_TEXT_HEIGHT_DP * getResources().getDisplayMetrics().density);
+ mTextHeight = typedArray.getDimensionPixelSize(
+ R.styleable.CustomVirtualView_internalTextSize, defaultHeight);
+ typedArray.recycle();
+ resetCoordinates();
+ }
+
+ protected void resetCoordinates() {
+ mTextPaint.setStyle(Style.FILL);
+ mTextPaint.setTextSize(mTextHeight);
+ mTopMargin = getPaddingTop();
+ mLeftMargin = getPaddingStart();
+ mLineLength = mTextHeight + VERTICAL_GAP;
+ }
+
+ @Override
+ public void autofill(SparseArray values) {
+ Context context = getContext();
+
+ // User has just selected a Dataset from the list of autofill suggestions.
+ // The Dataset is comprised of a list of AutofillValues, with each AutofillValue meant
+ // to fill a specific autofillable view. Now we have to update the UI based on the
+ // AutofillValues in the list, but first we make sure all autofilled values belong to the
+ // same partition
+ if (DEBUG) Log.d(TAG, "autofill(): " + values);
+
+ // First get the name of all partitions in the values
+ ArraySet partitions = new ArraySet<>();
+ for (int i = 0; i < values.size(); i++) {
+ int id = values.keyAt(i);
+ Partition partition = mPartitionsByAutofillId.get(id);
+ if (partition == null) {
+ showError(context.getString(R.string.message_autofill_no_partitions, id,
+ mPartitionsByAutofillId));
+ return;
+ }
+ partitions.add(partition.mName);
+ }
+
+ // Then make sure they follow the Highlander rule (There can be only one)
+ if (partitions.size() != 1) {
+ showError(context.getString(R.string.message_autofill_blocked, partitions));
+ return;
+ }
+
+ // Finally, autofill it.
+ DateFormat df = android.text.format.DateFormat.getDateFormat(context);
+ for (int i = 0; i < values.size(); i++) {
+ int id = values.keyAt(i);
+ AutofillValue value = values.valueAt(i);
+ Item item = mVirtualViews.get(id);
+
+ if (item == null) {
+ Log.w(TAG, "No item for id " + id);
+ continue;
+ }
+
+ if (!item.editable) {
+ showError(context.getString(R.string.message_autofill_readonly, item.text));
+ continue;
+ }
+
+ // Check if the type was properly set by the autofill service
+ if (DEBUG) {
+ Log.d(TAG, "Validating " + i
+ + ": expectedType=" + Util.getAutofillTypeAsString(item.type)
+ + "(" + item.type + "), value=" + value);
+ }
+ boolean valid = false;
+ if (value.isText() && item.type == AUTOFILL_TYPE_TEXT) {
+ item.text = value.getTextValue();
+ valid = true;
+ } else if (value.isDate() && item.type == AUTOFILL_TYPE_DATE) {
+ item.text = df.format(new Date(value.getDateValue()));
+ valid = true;
+ } else {
+ Log.w(TAG, "Unsupported type: " + value);
+ }
+ if (!valid) {
+ item.text = context.getString(R.string.message_autofill_invalid);
+ }
+ }
+ postInvalidate();
+ showMessage(context.getString(R.string.message_autofill_ok, partitions.valueAt(0)));
+ }
+
+ @Override
+ public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+ // Build a ViewStructure that will get passed to the AutofillService by the framework
+ // when it is time to find autofill suggestions.
+ structure.setClassName(getClass().getName());
+ int childrenSize = mVirtualViews.size();
+ if (DEBUG) {
+ Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags + ", items = "
+ + childrenSize + ", extras: " + bundleToString(structure.getExtras()));
+ }
+ int index = structure.addChildCount(childrenSize);
+ // Traverse through the view hierarchy, including virtual child views. For each view, we
+ // need to set the relevant autofill metadata and add it to the ViewStructure.
+ for (int i = 0; i < childrenSize; i++) {
+ Item item = mVirtualViews.valueAt(i);
+ if (DEBUG) Log.d(TAG, "Adding new child at index " + index + ": " + item);
+ ViewStructure child = structure.newChild(index);
+ child.setAutofillId(structure.getAutofillId(), item.id);
+ child.setAutofillHints(item.hints);
+ child.setAutofillType(item.type);
+ child.setAutofillValue(item.getAutofillValue());
+ child.setDataIsSensitive(!item.sanitized);
+ child.setFocused(item.focused);
+ child.setVisibility(View.VISIBLE);
+ child.setDimens(item.line.mBounds.left, item.line.mBounds.top, 0, 0,
+ item.line.mBounds.width(), item.line.mBounds.height());
+ child.setId(item.id, getContext().getPackageName(), null, item.idEntry);
+ child.setClassName(item.getClassName());
+ child.setDimens(item.line.mBounds.left, item.line.mBounds.top, 0, 0,
+ item.line.mBounds.width(), item.line.mBounds.height());
+ index++;
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (VERBOSE) {
+ Log.v(TAG, "onDraw(): " + mVirtualViewGroups.size() + " lines; canvas:" + canvas);
+ }
+ float x;
+ float y = mTopMargin + mLineLength;
+ for (int i = 0; i < mVirtualViewGroups.size(); i++) {
+ Line line = mVirtualViewGroups.get(i);
+ x = mLeftMargin;
+ if (VERBOSE) Log.v(TAG, "Drawing '" + line + "' at " + x + "x" + y);
+ mTextPaint.setColor(line.mFieldTextItem.focused ? FOCUSED_COLOR : UNFOCUSED_COLOR);
+ String readOnlyText = line.mLabelItem.text + ": [";
+ String writeText = line.mFieldTextItem.text + "]";
+ // Paints the label first...
+ canvas.drawText(readOnlyText, x, y, mTextPaint);
+ // ...then paints the edit text and sets the proper boundary
+ float deltaX = mTextPaint.measureText(readOnlyText);
+ x += deltaX;
+ line.mBounds.set((int) x, (int) (y - mLineLength),
+ (int) (x + mTextPaint.measureText(writeText)), (int) y);
+ if (VERBOSE) Log.v(TAG, "setBounds(" + x + ", " + y + "): " + line.mBounds);
+ canvas.drawText(writeText, x, y, mTextPaint);
+ y += mLineLength;
+
+ if (DRAW_AUTOFILL_UI_AFTER_SCROLL) {
+ line.notifyFocusChanged();
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int y = (int) event.getY();
+ onMotion(y);
+ return super.onTouchEvent(event);
+ }
+
+ /**
+ * Handles a motion event.
+ *
+ * @param y y coordinate.
+ */
+ protected void onMotion(int y) {
+ if (DEBUG) {
+ Log.d(TAG, "onMotion(): y=" + y + ", range=" + mLineLength + ", top=" + mTopMargin);
+ }
+ int lowerY = mTopMargin;
+ int upperY = -1;
+ for (int i = 0; i < mVirtualViewGroups.size(); i++) {
+ Line line = mVirtualViewGroups.get(i);
+ upperY = lowerY + mLineLength;
+ if (DEBUG) Log.d(TAG, "Line " + i + " ranges from " + lowerY + " to " + upperY);
+ if (lowerY <= y && y <= upperY) {
+ if (mFocusedLine != null) {
+ Log.d(TAG, "Removing focus from " + mFocusedLine);
+ mFocusedLine.changeFocus(false);
+ }
+ Log.d(TAG, "Changing focus to " + line);
+ mFocusedLine = line;
+ mFocusedLine.changeFocus(true);
+ invalidate();
+ break;
+ }
+ lowerY += mLineLength;
+ }
+ }
+
+ /**
+ * Creates a new partition with the given name.
+ *
+ * @throws IllegalArgumentException if such partition already exists.
+ */
+ public Partition addPartition(String name) {
+ Preconditions.checkNotNull(name, "Name cannot be null.");
+ Preconditions.checkArgument(!mPartitionsByName.containsKey(name),
+ "Partition with such name already exists.");
+ Partition partition = new Partition(name);
+ mPartitionsByName.put(name, partition);
+ return partition;
+ }
+
+ private void showError(String message) {
+ showMessage(true, message);
+ }
+
+ private void showMessage(String message) {
+ showMessage(false, message);
+ }
+
+ private void showMessage(boolean warning, String message) {
+ if (warning) {
+ Log.w(TAG, message);
+ } else {
+ Log.i(TAG, message);
+ }
+ Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
+ }
+
+
+ protected static final class Item {
+ protected final int id;
+ private final String idEntry;
+ private final Line line;
+ private final boolean editable;
+ private final boolean sanitized;
+ private final String[] hints;
+ private final int type;
+ private CharSequence text;
+ private boolean focused = false;
+ private long date;
+
+ Item(Line line, int id, String idEntry, String[] hints, int type, CharSequence text,
+ boolean editable, boolean sanitized) {
+ this.line = line;
+ this.id = id;
+ this.idEntry = idEntry;
+ this.text = text;
+ this.editable = editable;
+ this.sanitized = sanitized;
+ this.hints = hints;
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return id + "/" + idEntry + ": "
+ + (type == AUTOFILL_TYPE_DATE ? date : text) // TODO: use DateFormat for date
+ + " (" + Util.getAutofillTypeAsString(type) + ")"
+ + (editable ? " (editable)" : " (read-only)"
+ + (sanitized ? " (sanitized)" : " (sensitive"))
+ + (hints == null ? " (no hints)" : " ( " + Arrays.toString(hints) + ")");
+ }
+
+ public String getClassName() {
+ return editable ? EditText.class.getName() : TextView.class.getName();
+ }
+
+ public AutofillValue getAutofillValue() {
+ switch (type) {
+ case AUTOFILL_TYPE_TEXT:
+ return (TextUtils.getTrimmedLength(text) > 0)
+ ? AutofillValue.forText(text)
+ : null;
+ case AUTOFILL_TYPE_DATE:
+ return AutofillValue.forDate(date);
+ default:
+ return null;
+ }
+ }
+ }
+
+ /**
+ * A partition represents a logical group of items, such as credit card info.
+ */
+ public final class Partition {
+ private final String mName;
+ private final SparseArray mLines = new SparseArray<>();
+
+ private Partition(String name) {
+ mName = name;
+ }
+
+ /**
+ * Adds a new line (containining a label and an input field) to the view.
+ *
+ * @param idEntryPrefix id prefix used to identify the line - label node will be suffixed
+ * with {@code Label} and editable node with {@code Field}.
+ * @param autofillType {@link View#getAutofillType() autofill type} of the field.
+ * @param label text used in the label.
+ * @param text initial text used in the input field.
+ * @param sensitive whether the input is considered sensitive.
+ * @param autofillHints list of autofill hints.
+ * @return the new line.
+ */
+ public Line addLine(String idEntryPrefix, int autofillType, String label, String text,
+ boolean sensitive, String... autofillHints) {
+ Preconditions.checkArgument(autofillType == AUTOFILL_TYPE_TEXT ||
+ autofillType == AUTOFILL_TYPE_DATE, "Unsupported type: " + autofillType);
+ Line line = new Line(idEntryPrefix, autofillType, label, autofillHints, text,
+ !sensitive);
+ mVirtualViewGroups.add(line);
+ int id = line.mFieldTextItem.id;
+ mLines.put(id, line);
+ mVirtualViews.put(line.mLabelItem.id, line.mLabelItem);
+ mVirtualViews.put(id, line.mFieldTextItem);
+ mPartitionsByAutofillId.put(id, this);
+
+ return line;
+ }
+
+ /**
+ * Resets the value of all items in the partition.
+ */
+ public void reset() {
+ for (int i = 0; i < mLines.size(); i++) {
+ mLines.valueAt(i).reset();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+ }
+
+ /**
+ * A line in the virtual view contains a label and an input field.
+ */
+ public final class Line {
+
+ protected final Item mFieldTextItem;
+ // Boundaries of the text field, relative to the CustomView
+ private final Rect mBounds = new Rect();
+ private final Item mLabelItem;
+ private final int mAutofillType;
+
+ private Line(String idEntryPrefix, int autofillType, String label, String[] hints,
+ String text, boolean sanitized) {
+ this.mAutofillType = autofillType;
+ this.mLabelItem = new Item(this, ++sNextId, idEntryPrefix + "Label", null,
+ AUTOFILL_TYPE_NONE, label, false, true);
+ this.mFieldTextItem = new Item(this, ++sNextId, idEntryPrefix + "Field", hints,
+ autofillType, text, true, sanitized);
+ }
+
+ private void changeFocus(boolean focused) {
+ mFieldTextItem.focused = focused;
+ notifyFocusChanged();
+ }
+
+ void notifyFocusChanged() {
+ if (mFieldTextItem.focused) {
+ Rect absBounds = getAbsCoordinates();
+ if (DEBUG) {
+ Log.d(TAG, "focus gained on " + mFieldTextItem.id + "; absBounds=" + absBounds);
+ }
+ mAutofillManager.notifyViewEntered(CustomVirtualView.this, mFieldTextItem.id,
+ absBounds);
+ } else {
+ if (DEBUG) Log.d(TAG, "focus lost on " + mFieldTextItem.id);
+ mAutofillManager.notifyViewExited(CustomVirtualView.this, mFieldTextItem.id);
+ }
+ }
+
+ private Rect getAbsCoordinates() {
+ // Must offset the boundaries so they're relative to the CustomView.
+ int offset[] = new int[2];
+ getLocationOnScreen(offset);
+ Rect absBounds = new Rect(mBounds.left + offset[0],
+ mBounds.top + offset[1],
+ mBounds.right + offset[0], mBounds.bottom + offset[1]);
+ if (VERBOSE) {
+ Log.v(TAG, "getAbsCoordinates() for " + mFieldTextItem.id + ": bounds=" + mBounds
+ + " offset: " + Arrays.toString(offset) + " absBounds: " + absBounds);
+ }
+ return absBounds;
+ }
+
+ /**
+ * Gets the value of the input field text.
+ */
+ public CharSequence getText() {
+ return mFieldTextItem.text;
+ }
+
+ /**
+ * Resets the value of the input field text.
+ */
+ public void reset() {
+ mFieldTextItem.text = " ";
+ }
+
+ @Override
+ public String toString() {
+ return "Label: " + mLabelItem + " Text: " + mFieldTextItem + " Focused: " +
+ mFieldTextItem.focused + " Type: " + mAutofillType;
+ }
+ }
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/ScrollableCustomVirtualView.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/ScrollableCustomVirtualView.java
new file mode 100644
index 0000000000000000000000000000000000000000..cdc9d98980e3b3657ce53915a4c01dfd759139a0
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/autofillable/ScrollableCustomVirtualView.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.view.autofillable;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+/**
+ * A version of {@link CustomVirtualView} that uses gesture to provide scrolling.
+ */
+public class ScrollableCustomVirtualView extends CustomVirtualView
+ implements GestureDetector.OnGestureListener {
+
+ private static final String TAG = "ScrollableCustomView";
+
+ private GestureDetector mGestureDetector;
+
+ public ScrollableCustomVirtualView(Context context) {
+ this(context, null);
+ }
+
+ public ScrollableCustomVirtualView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+
+ }
+
+ public ScrollableCustomVirtualView(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ScrollableCustomVirtualView(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mGestureDetector = new GestureDetector(context, this);
+ }
+
+ /**
+ * Resets the UI to the intial state.
+ */
+ public void resetPositions() {
+ super.resetCoordinates();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return mGestureDetector.onTouchEvent(event);
+ }
+
+ /*
+ * Methods below implement GestureDetector.OnGestureListener
+ */
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if (VERBOSE) Log.v(TAG, "onScroll(): " + distanceX + " - " + distanceY);
+ if (mFocusedLine != null) {
+ mAutofillManager.notifyViewExited(this, mFocusedLine.mFieldTextItem.id);
+ }
+ mTopMargin -= distanceY;
+ mLeftMargin -= distanceX;
+ invalidate();
+ return true;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent event) {
+ onMotion((int) event.getY());
+ return true;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/InfoButton.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/InfoButton.java
new file mode 100644
index 0000000000000000000000000000000000000000..92a221fc35cdd41e8e786e02edaa30da7aa9c185
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/InfoButton.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.view.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.AppCompatImageButton;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.example.android.autofill.app.R;
+
+public class InfoButton extends AppCompatImageButton {
+ public InfoButton(Context context) {
+ this(context, null);
+ }
+
+ public InfoButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public InfoButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.InfoButton,
+ defStyleAttr, 0);
+ String infoText = typedArray.getString(R.styleable.InfoButton_dialogText);
+ typedArray.recycle();
+ setInfoText(infoText);
+ }
+
+ public void setInfoText(final String infoText) {
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ new AlertDialog.Builder(InfoButton.this.getContext())
+ .setMessage(infoText).create().show();
+ }
+ });
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/NavigationItem.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/NavigationItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..9fea6bf499ec2e2e82355068a9ecc196afcb0901
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/view/widget/NavigationItem.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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.view.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.CardView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.example.android.autofill.app.R;
+
+import static com.example.android.autofill.app.Util.TAG;
+
+public class NavigationItem extends FrameLayout {
+ public NavigationItem(Context context) {
+ this(context, null);
+ }
+
+ public NavigationItem(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NavigationItem(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NavigationItem(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NavigationItem,
+ defStyleAttr, defStyleRes);
+ String labelText = typedArray.getString(R.styleable.NavigationItem_labelText);
+ String infoText = typedArray.getString(R.styleable.NavigationItem_infoText);
+ Drawable logoDrawable = typedArray.getDrawable(R.styleable.NavigationItem_itemLogo);
+ @ColorRes int colorRes = typedArray.getResourceId(R.styleable.NavigationItem_imageColor, 0);
+ String launchingActivityName = typedArray.getString(R.styleable.NavigationItem_destinationActivityName);
+ int imageColor = ContextCompat.getColor(getContext(), colorRes);
+ typedArray.recycle();
+ View rootView = LayoutInflater.from(context).inflate(R.layout.navigation_item, this);
+ TextView buttonLabel = rootView.findViewById(R.id.buttonLabel);
+ buttonLabel.setText(labelText);
+ if (logoDrawable != null) {
+ Drawable mutatedLogoDrawable = logoDrawable.mutate();
+ mutatedLogoDrawable.setColorFilter(imageColor, PorterDuff.Mode.SRC_IN);
+ buttonLabel.setCompoundDrawablesRelativeWithIntrinsicBounds(mutatedLogoDrawable, null,
+ null, null);
+ }
+ InfoButton infoButton = rootView.findViewById(R.id.infoButton);
+ infoButton.setInfoText(infoText);
+ infoButton.setColorFilter(imageColor);
+ CardView outerView = rootView.findViewById(R.id.cardView);
+ outerView.setOnClickListener((view) -> {
+ if (launchingActivityName != null) {
+ Intent intent = new Intent();
+ intent.setClassName(getContext().getPackageName(), launchingActivityName);
+ context.startActivity(intent);
+ } else {
+ Log.w(TAG, "Launching Activity name not set.");
+ }
+ });
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/CommonUtil.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/CommonUtil.java
deleted file mode 100644
index 9e95eaf1acccfa46950fab9549ead1adfea89bca..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/CommonUtil.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework;
-
-import android.os.Bundle;
-
-import java.util.Arrays;
-import java.util.Set;
-
-public final class CommonUtil {
-
- public static final String TAG = "AutofillSample";
- public static final boolean DEBUG = true;
- public static final String EXTRA_DATASET_NAME = "dataset_name";
- public static final String EXTRA_FOR_RESPONSE = "for_response";
-
- private static void bundleToString(StringBuilder builder, Bundle data) {
- final Set keySet = data.keySet();
- builder.append("[Bundle with ").append(keySet.size()).append(" keys:");
- for (String key : keySet) {
- builder.append(' ').append(key).append('=');
- Object value = data.get(key);
- if ((value instanceof Bundle)) {
- bundleToString(builder, (Bundle) value);
- } else {
- builder.append((value instanceof Object[])
- ? Arrays.toString((Object[]) value) : value);
- }
- }
- builder.append(']');
- }
-
- public static String bundleToString(Bundle data) {
- if (data == null) {
- return "N/A";
- }
- final StringBuilder builder = new StringBuilder();
- bundleToString(builder, data);
- return builder.toString();
- }
-}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CreditCardActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CreditCardActivity.java
deleted file mode 100644
index 7feb39369a9f4a1df7c98e08c0008ff3f87ac81e..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CreditCardActivity.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.app;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-import android.view.View;
-import android.widget.ArrayAdapter;
-import android.widget.Spinner;
-
-import com.example.android.autofillframework.R;
-
-public class CreditCardActivity extends AppCompatActivity {
-
- public static Intent getStartActivityIntent(Context context) {
- Intent intent = new Intent(context, CreditCardActivity.class);
- return intent;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.credit_card_activity);
-
- Spinner ccExpirationDaySpinner = findViewById(R.id.expirationDay);
- Spinner ccExpirationMonthSpinner = findViewById(R.id.expirationMonth);
- Spinner ccExpirationYearSpinner = findViewById(R.id.expirationYear);
-
- // Create an ArrayAdapter using the string array and a default spinner layout
- ArrayAdapter dayAdapter = ArrayAdapter.createFromResource
- (this, R.array.day_array, android.R.layout.simple_spinner_item);
- // Specify the layout to use when the list of choices appears
- dayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- // Apply the adapter to the spinner
- ccExpirationDaySpinner.setAdapter(dayAdapter);
-
- ArrayAdapter monthAdapter = ArrayAdapter.createFromResource
- (this, R.array.month_array, android.R.layout.simple_spinner_item);
- monthAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- ccExpirationMonthSpinner.setAdapter(monthAdapter);
-
- ArrayAdapter yearAdapter = ArrayAdapter.createFromResource
- (this, R.array.year_array, android.R.layout.simple_spinner_item);
- yearAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- ccExpirationYearSpinner.setAdapter(yearAdapter);
-
- findViewById(R.id.submit).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- submit();
- }
- });
- findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- resetFields();
- }
- });
- }
-
- private void resetFields() {
- //TODO
- }
-
- /**
- * Launches new Activity and finishes, triggering an autofill save request if the user entered
- * any new data.
- */
- private void submit() {
- Intent intent = WelcomeActivity.getStartActivityIntent(CreditCardActivity.this);
- startActivity(intent);
- finish();
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.java
deleted file mode 100644
index d4efc6a6c4bbc0d26071e5db5888e34f2c3a6ccd..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.app;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewStructure;
-import android.view.autofill.AutofillManager;
-import android.view.autofill.AutofillValue;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import com.example.android.autofillframework.R;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-import static com.example.android.autofillframework.CommonUtil.bundleToString;
-
-
-/**
- * Custom View with virtual child views for Username/Password text fields.
- */
-public class CustomVirtualView extends View {
-
- private static final String TAG = "CustomView";
-
- private static final int TOP_MARGIN = 100;
- private static final int LEFT_MARGIN = 100;
- private static final int TEXT_HEIGHT = 90;
- private static final int VERTICAL_GAP = 10;
- private static final int LINE_HEIGHT = TEXT_HEIGHT + VERTICAL_GAP;
- private static final int UNFOCUSED_COLOR = Color.BLACK;
- private static final int FOCUSED_COLOR = Color.RED;
- private static int sNextId;
-
- private final ArrayList mVirtualViewGroups = new ArrayList<>();
- private final SparseArray- mVirtualViews = new SparseArray<>();
- private final AutofillManager mAutofillManager;
-
- private Line mFocusedLine;
- private Paint mTextPaint;
-
- private Line mUsernameLine;
- private Line mPasswordLine;
-
- public CustomVirtualView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mAutofillManager = context.getSystemService(AutofillManager.class);
- mTextPaint = new Paint();
- mTextPaint.setStyle(Style.FILL);
- mTextPaint.setTextSize(TEXT_HEIGHT);
- mUsernameLine = addLine("usernameField", context.getString(R.string.username_label),
- new String[]{View.AUTOFILL_HINT_USERNAME}, " ", true);
- mPasswordLine = addLine("passwordField", context.getString(R.string.password_label),
- new String[]{View.AUTOFILL_HINT_PASSWORD}, " ", false);
- }
-
- @Override
- public void autofill(SparseArray
values) {
- // User has just selected a Dataset from the list of autofill suggestions.
- // The Dataset is comprised of a list of AutofillValues, with each AutofillValue meant
- // to fill a specific autofillable view. Now we have to update the UI based on the
- // AutofillValues in the list.
- Log.d(TAG, "autoFill(): " + values);
- for (int i = 0; i < values.size(); i++) {
- int id = values.keyAt(i);
- AutofillValue value = values.valueAt(i);
- Item item = mVirtualViews.get(id);
- if (item != null && item.editable) {
- // Set the item's text to the text wrapped in the AutofillValue.
- item.text = value.getTextValue();
- } else if (item == null) {
- Log.w(TAG, "No item for id " + id);
- } else {
- Log.w(TAG, "Item for id " + id + " is not editable: " + item);
- }
- }
- postInvalidate();
- }
-
-
- @Override
- public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
- // Build a ViewStructure that will get passed to the AutofillService by the framework
- // when it is time to find autofill suggestions.
- structure.setClassName(getClass().getName());
- int childrenSize = mVirtualViews.size();
- Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags + ", items = "
- + childrenSize + ", extras: " + bundleToString(structure.getExtras()));
- int index = structure.addChildCount(childrenSize);
- // Traverse through the view hierarchy, including virtual child views. For each view, we
- // need to set the relevant autofill metadata and add it to the ViewStructure.
- for (int i = 0; i < childrenSize; i++) {
- Item item = mVirtualViews.valueAt(i);
- Log.d(TAG, "Adding new child at index " + index + ": " + item);
- ViewStructure child = structure.newChild(index);
- child.setAutofillId(structure.getAutofillId(), item.id);
- child.setAutofillHints(item.hints);
- child.setAutofillType(item.type);
- child.setDataIsSensitive(!item.sanitized);
- child.setText(item.text);
- child.setAutofillValue(AutofillValue.forText(item.text));
- child.setFocused(item.focused);
- child.setId(item.id, getContext().getPackageName(), null, item.line.idEntry);
- child.setClassName(item.getClassName());
- index++;
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- Log.d(TAG, "onDraw: " + mVirtualViewGroups.size() + " lines; canvas:" + canvas);
- float x;
- float y = TOP_MARGIN + LINE_HEIGHT;
- for (int i = 0; i < mVirtualViewGroups.size(); i++) {
- x = LEFT_MARGIN;
- Line line = mVirtualViewGroups.get(i);
- Log.v(TAG, "Drawing '" + line + "' at " + x + "x" + y);
- mTextPaint.setColor(line.fieldTextItem.focused ? FOCUSED_COLOR : UNFOCUSED_COLOR);
- String readOnlyText = line.labelItem.text + ": [";
- String writeText = line.fieldTextItem.text + "]";
- // Paints the label first...
- canvas.drawText(readOnlyText, x, y, mTextPaint);
- // ...then paints the edit text and sets the proper boundary
- float deltaX = mTextPaint.measureText(readOnlyText);
- x += deltaX;
- line.bounds.set((int) x, (int) (y - LINE_HEIGHT),
- (int) (x + mTextPaint.measureText(writeText)), (int) y);
- Log.d(TAG, "setBounds(" + x + ", " + y + "): " + line.bounds);
- canvas.drawText(writeText, x, y, mTextPaint);
- y += LINE_HEIGHT;
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int y = (int) event.getY();
- Log.d(TAG, "Touched: y=" + y + ", range=" + LINE_HEIGHT + ", top=" + TOP_MARGIN);
- int lowerY = TOP_MARGIN;
- int upperY = -1;
- for (int i = 0; i < mVirtualViewGroups.size(); i++) {
- upperY = lowerY + LINE_HEIGHT;
- Line line = mVirtualViewGroups.get(i);
- Log.d(TAG, "Line " + i + " ranges from " + lowerY + " to " + upperY);
- if (lowerY <= y && y <= upperY) {
- if (mFocusedLine != null) {
- Log.d(TAG, "Removing focus from " + mFocusedLine);
- mFocusedLine.changeFocus(false);
- }
- Log.d(TAG, "Changing focus to " + line);
- mFocusedLine = line;
- mFocusedLine.changeFocus(true);
- invalidate();
- break;
- }
- lowerY += LINE_HEIGHT;
- }
- return super.onTouchEvent(event);
- }
-
- public CharSequence getUsernameText() {
- return mUsernameLine.fieldTextItem.text;
- }
-
- public CharSequence getPasswordText() {
- return mPasswordLine.fieldTextItem.text;
- }
-
- public void resetFields() {
- mUsernameLine.reset();
- mPasswordLine.reset();
- postInvalidate();
- }
-
- private Line addLine(String idEntry, String label, String[] hints, String text,
- boolean sanitized) {
- Line line = new Line(idEntry, label, hints, text, sanitized);
- mVirtualViewGroups.add(line);
- mVirtualViews.put(line.labelItem.id, line.labelItem);
- mVirtualViews.put(line.fieldTextItem.id, line.fieldTextItem);
- return line;
- }
-
- private static final class Item {
- private final Line line;
- private final int id;
- private final boolean editable;
- private final boolean sanitized;
- private final String[] hints;
- private final int type;
- private CharSequence text;
- private boolean focused = false;
-
- Item(Line line, int id, String[] hints, int type, CharSequence text, boolean editable,
- boolean sanitized) {
- this.line = line;
- this.id = id;
- this.text = text;
- this.editable = editable;
- this.sanitized = sanitized;
- this.hints = hints;
- this.type = type;
- }
-
- @Override
- public String toString() {
- return id + ": " + text + (editable ? " (editable)" : " (read-only)"
- + (sanitized ? " (sanitized)" : " (sensitive"));
- }
-
- public String getClassName() {
- return editable ? EditText.class.getName() : TextView.class.getName();
- }
- }
-
- private final class Line {
-
- // Boundaries of the text field, relative to the CustomView
- final Rect bounds = new Rect();
- private Item labelItem;
- private Item fieldTextItem;
- private String idEntry;
-
- private Line(String idEntry, String label, String[] hints, String text, boolean sanitized) {
- this.idEntry = idEntry;
- this.labelItem = new Item(this, ++sNextId, null, AUTOFILL_TYPE_NONE, label,
- false, true);
- this.fieldTextItem = new Item(this, ++sNextId, hints, AUTOFILL_TYPE_TEXT, text,
- true, sanitized);
- }
-
- void changeFocus(boolean focused) {
- fieldTextItem.focused = focused;
- if (focused) {
- Rect absBounds = getAbsCoordinates();
- Log.d(TAG, "focus gained on " + fieldTextItem.id + "; absBounds=" + absBounds);
- mAutofillManager.notifyViewEntered(CustomVirtualView.this, fieldTextItem.id,
- absBounds);
- } else {
- Log.d(TAG, "focus lost on " + fieldTextItem.id);
- mAutofillManager.notifyViewExited(CustomVirtualView.this, fieldTextItem.id);
- }
- }
-
- private Rect getAbsCoordinates() {
- // Must offset the boundaries so they're relative to the CustomView.
- int offset[] = new int[2];
- getLocationOnScreen(offset);
- Rect absBounds = new Rect(bounds.left + offset[0],
- bounds.top + offset[1],
- bounds.right + offset[0], bounds.bottom + offset[1]);
- Log.v(TAG, "getAbsCoordinates() for " + fieldTextItem.id + ": bounds=" + bounds
- + " offset: " + Arrays.toString(offset) + " absBounds: " + absBounds);
- return absBounds;
- }
-
- public void reset() {
- fieldTextItem.text = " ";
- }
-
- @Override
- public String toString() {
- return "Label: " + labelItem + " Text: " + fieldTextItem + " Focused: " +
- fieldTextItem.focused;
- }
- }
-}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/MainActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/MainActivity.java
deleted file mode 100644
index cf76aee1e508fbfa6e1ad3e5c72b59cef35134b5..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/MainActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.app;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
-import android.view.View;
-
-import com.example.android.autofillframework.R;
-
-/**
- * This is used to launch sample activities that showcase autofill.
- */
-public class MainActivity extends AppCompatActivity implements View.OnClickListener {
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- findViewById(R.id.standardViewSignInButton).setOnClickListener(this);
- findViewById(R.id.virtualViewSignInButton).setOnClickListener(this);
- findViewById(R.id.creditCardCheckoutButton).setOnClickListener(this);
- findViewById(R.id.standardLoginWithAutoCompleteButton).setOnClickListener(this);
- }
-
- @Override
- public void onClick(View view) {
- Intent intent = null;
- switch (view.getId()) {
- case R.id.standardViewSignInButton:
- intent = StandardSignInActivity.getStartActivityIntent(this);
- break;
- case R.id.virtualViewSignInButton:
- intent = VirtualSignInActivity.getStartActivityIntent(this);
- break;
- case R.id.creditCardCheckoutButton:
- intent = CreditCardActivity.getStartActivityIntent(this);
- break;
- case R.id.standardLoginWithAutoCompleteButton:
- intent = StandardAutoCompleteSignInActivity.getStartActivityIntent(this);
- break;
- }
- if (intent != null) {
- startActivity(intent);
- }
- }
-}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.java
deleted file mode 100644
index 30591c0e8e86f0ddac5d93ad8c714f079e4c343e..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.assist.AssistStructure;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.support.annotation.Nullable;
-import android.text.Editable;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.EditText;
-import android.widget.Toast;
-
-import com.example.android.autofillframework.R;
-import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsAutofillRepository;
-import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection;
-import com.example.android.autofillframework.multidatasetservice.settings.MyPreferences;
-
-import java.util.HashMap;
-
-import static android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE;
-import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT;
-import static com.example.android.autofillframework.CommonUtil.EXTRA_DATASET_NAME;
-import static com.example.android.autofillframework.CommonUtil.EXTRA_FOR_RESPONSE;
-import static com.example.android.autofillframework.CommonUtil.TAG;
-
-/**
- * This Activity controls the UI for logging in to the Autofill service.
- * It is launched when an Autofill Response or specific Dataset within the Response requires
- * authentication to access. It bundles the result in an Intent.
- */
-public class AuthActivity extends Activity {
-
- // Unique id for dataset intents.
- private static int sDatasetPendingIntentId = 0;
-
- private EditText mMasterPassword;
- private Intent mReplyIntent;
-
- static IntentSender getAuthIntentSenderForResponse(Context context) {
- final Intent intent = new Intent(context, AuthActivity.class);
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
- .getIntentSender();
- }
-
- static IntentSender getAuthIntentSenderForDataset(Context context, String datasetName) {
- final Intent intent = new Intent(context, AuthActivity.class);
- intent.putExtra(EXTRA_DATASET_NAME, datasetName);
- intent.putExtra(EXTRA_FOR_RESPONSE, false);
- return PendingIntent.getActivity(context, ++sDatasetPendingIntentId, intent,
- PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
- }
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.multidataset_service_auth_activity);
- mMasterPassword = findViewById(R.id.master_password);
- findViewById(R.id.login).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- login();
- }
-
- });
- findViewById(R.id.cancel).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onFailure();
- AuthActivity.this.finish();
- }
- });
- }
-
- private void login() {
- Editable password = mMasterPassword.getText();
- if (password.toString()
- .equals(MyPreferences.getInstance(AuthActivity.this).getMasterPassword())) {
- onSuccess();
- } else {
- Toast.makeText(this, "Password incorrect", Toast.LENGTH_SHORT).show();
- onFailure();
- }
- finish();
- }
-
- @Override
- public void finish() {
- if (mReplyIntent != null) {
- setResult(RESULT_OK, mReplyIntent);
- } else {
- setResult(RESULT_CANCELED);
- }
- super.finish();
- }
-
- private void onFailure() {
- Log.w(TAG, "Failed auth.");
- mReplyIntent = null;
- }
-
- private void onSuccess() {
- Intent intent = getIntent();
- boolean forResponse = intent.getBooleanExtra(EXTRA_FOR_RESPONSE, true);
- AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
- StructureParser parser = new StructureParser(structure);
- parser.parseForFill();
- AutofillFieldMetadataCollection autofillFields = parser.getAutofillFields();
- int saveTypes = autofillFields.getSaveType();
- mReplyIntent = new Intent();
- HashMap clientFormDataMap =
- SharedPrefsAutofillRepository.getInstance(this).getClientFormData
- (autofillFields.getFocusedHints(), autofillFields.getAllHints());
- if (forResponse) {
- setResponseIntent(AutofillHelper.newResponse
- (this, false, autofillFields, clientFormDataMap));
- } else {
- String datasetName = intent.getStringExtra(EXTRA_DATASET_NAME);
- setDatasetIntent(AutofillHelper.newDataset
- (this, autofillFields, clientFormDataMap.get(datasetName), false));
- }
- }
-
- private void setResponseIntent(FillResponse fillResponse) {
- mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse);
- }
-
- private void setDatasetIntent(Dataset dataset) {
- mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset);
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillFieldMetadata.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillFieldMetadata.java
deleted file mode 100644
index b4cf8992098675e6eb6df8b364bf733dc749a0b1..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillFieldMetadata.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.service.autofill.SaveInfo;
-import android.view.View;
-import android.view.autofill.AutofillId;
-
-/**
- * A stripped down version of a {@link ViewNode} that contains only autofill-relevant metadata. It
- * also contains a {@code mSaveType} flag that is calculated based on the {@link ViewNode}]'s
- * autofill hints.
- */
-public class AutofillFieldMetadata {
- private int mSaveType = 0;
- private String[] mAutofillHints;
- private AutofillId mAutofillId;
- private int mAutofillType;
- private CharSequence[] mAutofillOptions;
- private boolean mFocused;
-
- public AutofillFieldMetadata(ViewNode view) {
- mAutofillId = view.getAutofillId();
- mAutofillType = view.getAutofillType();
- mAutofillOptions = view.getAutofillOptions();
- mFocused = view.isFocused();
- setHints(view.getAutofillHints());
- }
-
- public String[] getHints() {
- return mAutofillHints;
- }
-
- public void setHints(String[] hints) {
- mAutofillHints = hints;
- updateSaveTypeFromHints();
- }
-
- public int getSaveType() {
- return mSaveType;
- }
-
- public AutofillId getId() {
- return mAutofillId;
- }
-
- public int getAutofillType() {
- return mAutofillType;
- }
-
- /**
- * When the {@link ViewNode} is a list that the user needs to choose a string from (i.e. a
- * spinner), this is called to return the index of a specific item in the list.
- */
- public int getAutofillOptionIndex(String value) {
- for (int i = 0; i < mAutofillOptions.length; i++) {
- if (mAutofillOptions[i].toString().equals(value)) {
- return i;
- }
- }
- return -1;
- }
-
- public boolean isFocused() {
- return mFocused;
- }
-
- private void updateSaveTypeFromHints() {
- mSaveType = 0;
- if (mAutofillHints == null) {
- return;
- }
- for (String hint : mAutofillHints) {
- switch (hint) {
- 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_NUMBER:
- case View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE:
- mSaveType |= SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
- break;
- case View.AUTOFILL_HINT_EMAIL_ADDRESS:
- mSaveType |= SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
- break;
- case View.AUTOFILL_HINT_PHONE:
- case View.AUTOFILL_HINT_NAME:
- mSaveType |= SaveInfo.SAVE_DATA_TYPE_GENERIC;
- break;
- case View.AUTOFILL_HINT_PASSWORD:
- mSaveType |= SaveInfo.SAVE_DATA_TYPE_PASSWORD;
- mSaveType &= ~SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
- mSaveType &= ~SaveInfo.SAVE_DATA_TYPE_USERNAME;
- break;
- case View.AUTOFILL_HINT_POSTAL_ADDRESS:
- case View.AUTOFILL_HINT_POSTAL_CODE:
- mSaveType |= SaveInfo.SAVE_DATA_TYPE_ADDRESS;
- break;
- case View.AUTOFILL_HINT_USERNAME:
- mSaveType |= SaveInfo.SAVE_DATA_TYPE_USERNAME;
- break;
- }
- }
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillFieldMetadataCollection.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillFieldMetadataCollection.java
deleted file mode 100644
index f0a9e3467d55804056108da392abfd0e292d734b..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillFieldMetadataCollection.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice;
-
-import android.view.autofill.AutofillId;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Data structure that stores a collection of {@code AutofillFieldMetadata}s. Contains all of the
- * client's {@code View} hierarchy autofill-relevant metadata.
- */
-public final class AutofillFieldMetadataCollection {
-
- private final List mAutofillIds = new ArrayList<>();
- private final HashMap> mAutofillHintsToFieldsMap = new HashMap<>();
- private final List mAllAutofillHints = new ArrayList<>();
- private final List mFocusedAutofillHints = new ArrayList<>();
- private int mSize = 0;
- private int mSaveType = 0;
-
- public void add(AutofillFieldMetadata autofillFieldMetadata) {
- mSaveType |= autofillFieldMetadata.getSaveType();
- mSize++;
- mAutofillIds.add(autofillFieldMetadata.getId());
- List hintsList = Arrays.asList(autofillFieldMetadata.getHints());
- mAllAutofillHints.addAll(hintsList);
- if (autofillFieldMetadata.isFocused()) {
- mFocusedAutofillHints.addAll(hintsList);
- }
- for (String hint : autofillFieldMetadata.getHints()) {
- if (mAutofillHintsToFieldsMap.get(hint) == null) {
- mAutofillHintsToFieldsMap.put(hint, new ArrayList());
- }
- mAutofillHintsToFieldsMap.get(hint).add(autofillFieldMetadata);
- }
- }
-
- public int getSaveType() {
- return mSaveType;
- }
-
- public AutofillId[] getAutofillIds() {
- return mAutofillIds.toArray(new AutofillId[mSize]);
- }
-
- public List getFieldsForHint(String hint) {
- return mAutofillHintsToFieldsMap.get(hint);
- }
-
- public List getFocusedHints() {
- return mFocusedAutofillHints;
- }
-
- public List getAllHints() {
- return mAllAutofillHints;
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.java
deleted file mode 100644
index 6ba758b24c8980f3bd420bd7bc2b59a5864e9640..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice;
-
-import android.content.Context;
-import android.content.IntentSender;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.service.autofill.SaveInfo;
-import android.util.Log;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import com.example.android.autofillframework.R;
-import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection;
-
-import java.util.HashMap;
-import java.util.Set;
-
-import static com.example.android.autofillframework.CommonUtil.TAG;
-
-/**
- * This is a class containing helper methods for building Autofill Datasets and Responses.
- */
-public final class AutofillHelper {
-
- /**
- * Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the
- * client View.
- */
- public static Dataset newDataset(Context context,
- AutofillFieldMetadataCollection autofillFields, FilledAutofillFieldCollection filledAutofillFieldCollection, boolean datasetAuth) {
- String datasetName = filledAutofillFieldCollection.getDatasetName();
- if (datasetName != null) {
- Dataset.Builder datasetBuilder = new Dataset.Builder
- (newRemoteViews(context.getPackageName(), datasetName));
- if (datasetAuth) {
- IntentSender sender = AuthActivity.getAuthIntentSenderForDataset(context, datasetName);
- datasetBuilder.setAuthentication(sender);
- }
- boolean setValueAtLeastOnce = filledAutofillFieldCollection.applyToFields(autofillFields, datasetBuilder);
- if (setValueAtLeastOnce) {
- return datasetBuilder.build();
- }
- }
- return null;
- }
-
- public static RemoteViews newRemoteViews(String packageName, String remoteViewsText) {
- RemoteViews presentation = new RemoteViews(packageName, R.layout.multidataset_service_list_item);
- presentation.setTextViewText(R.id.text1, remoteViewsText);
- return presentation;
- }
-
- /**
- * Wraps autofill data in a Response object (essentially a series of Datasets) which can then
- * be sent back to the client View.
- */
- public static FillResponse newResponse(Context context,
- boolean datasetAuth, AutofillFieldMetadataCollection autofillFields,
- HashMap clientFormDataMap) {
- FillResponse.Builder responseBuilder = new FillResponse.Builder();
- if (clientFormDataMap != null) {
- Set datasetNames = clientFormDataMap.keySet();
- for (String datasetName : datasetNames) {
- FilledAutofillFieldCollection filledAutofillFieldCollection = clientFormDataMap.get(datasetName);
- if (filledAutofillFieldCollection != null) {
- Dataset dataset = newDataset(context, autofillFields, filledAutofillFieldCollection, datasetAuth);
- if (dataset != null) {
- responseBuilder.addDataset(dataset);
- }
- }
- }
- }
- if (autofillFields.getSaveType() != 0) {
- AutofillId[] autofillIds = autofillFields.getAutofillIds();
- responseBuilder.setSaveInfo
- (new SaveInfo.Builder(autofillFields.getSaveType(), autofillIds).build());
- return responseBuilder.build();
- } else {
- Log.d(TAG, "These fields are not meant to be saved by autofill.");
- return null;
- }
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.java
deleted file mode 100644
index 494a8bf1cdbecbc7da2067945dda3a742f988699..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice;
-
-import android.app.assist.AssistStructure;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillRequest;
-import android.service.autofill.FillResponse;
-import android.service.autofill.SaveCallback;
-import android.service.autofill.SaveRequest;
-import android.util.Log;
-import android.widget.RemoteViews;
-
-import com.example.android.autofillframework.R;
-import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsAutofillRepository;
-import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection;
-import com.example.android.autofillframework.multidatasetservice.settings.MyPreferences;
-
-import java.util.HashMap;
-import java.util.List;
-
-import static com.example.android.autofillframework.CommonUtil.TAG;
-import static com.example.android.autofillframework.CommonUtil.bundleToString;
-
-public class MyAutofillService extends AutofillService {
-
- @Override
- public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
- FillCallback callback) {
- AssistStructure structure = request.getFillContexts()
- .get(request.getFillContexts().size() - 1).getStructure();
- final Bundle data = request.getClientState();
- Log.d(TAG, "onFillRequest(): data=" + bundleToString(data));
-
- // Temporary hack for disabling autofill for components in this autofill service.
- // i.e. we don't want to autofill components in AuthActivity.
- if (structure.getActivityComponent().toShortString()
- .contains("com.example.android.autofillframework.service")) {
- callback.onSuccess(null);
- return;
- }
- cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
- @Override
- public void onCancel() {
- Log.w(TAG, "Cancel autofill not implemented in this sample.");
- }
- });
- // Parse AutoFill data in Activity
- StructureParser parser = new StructureParser(structure);
- parser.parseForFill();
- AutofillFieldMetadataCollection autofillFields = parser.getAutofillFields();
- FillResponse.Builder responseBuilder = new FillResponse.Builder();
- // Check user's settings for authenticating Responses and Datasets.
- boolean responseAuth = MyPreferences.getInstance(this).isResponseAuth();
- if (responseAuth) {
- // If the entire Autofill Response is authenticated, AuthActivity is used
- // to generate Response.
- IntentSender sender = AuthActivity.getAuthIntentSenderForResponse(this);
- RemoteViews presentation = AutofillHelper
- .newRemoteViews(getPackageName(), getString(R.string.autofill_sign_in_prompt));
- responseBuilder
- .setAuthentication(autofillFields.getAutofillIds(), sender, presentation);
- callback.onSuccess(responseBuilder.build());
- } else {
- boolean datasetAuth = MyPreferences.getInstance(this).isDatasetAuth();
- HashMap clientFormDataMap =
- SharedPrefsAutofillRepository.getInstance(this).getClientFormData
- (autofillFields.getFocusedHints(), autofillFields.getAllHints());
- FillResponse response = AutofillHelper.newResponse
- (this, datasetAuth, autofillFields, clientFormDataMap);
- callback.onSuccess(response);
- }
- }
-
- @Override
- public void onSaveRequest(SaveRequest request, SaveCallback callback) {
- List context = request.getFillContexts();
- final AssistStructure structure = context.get(context.size() - 1).getStructure();
- final Bundle data = request.getClientState();
- Log.d(TAG, "onSaveRequest(): data=" + bundleToString(data));
- StructureParser parser = new StructureParser(structure);
- parser.parseForSave();
- FilledAutofillFieldCollection filledAutofillFieldCollection = parser.getClientFormData();
- SharedPrefsAutofillRepository.getInstance(this).saveClientFormData(filledAutofillFieldCollection);
- }
-
- @Override
- public void onConnected() {
- Log.d(TAG, "onConnected");
- }
-
- @Override
- public void onDisconnected() {
- Log.d(TAG, "onDisconnected");
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.java
deleted file mode 100644
index e290881982ac744021d13b067791d2deff1e6036..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice;
-
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.app.assist.AssistStructure.WindowNode;
-import android.util.Log;
-
-import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection;
-import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillField;
-
-import static com.example.android.autofillframework.CommonUtil.TAG;
-
-/**
- * Parser for an AssistStructure object. This is invoked when the Autofill Service receives an
- * AssistStructure from the client Activity, representing its View hierarchy. In this sample, it
- * parses the hierarchy and collects autofill metadata from {@link ViewNode}s along the way.
- */
-final class StructureParser {
- private final AutofillFieldMetadataCollection mAutofillFields = new AutofillFieldMetadataCollection();
- private final AssistStructure mStructure;
- private FilledAutofillFieldCollection mFilledAutofillFieldCollection;
-
- StructureParser(AssistStructure structure) {
- mStructure = structure;
-
- }
-
- public void parseForFill() {
- parse(true);
- }
-
- public void parseForSave() {
- parse(false);
- }
-
- /**
- * Traverse AssistStructure and add ViewNode metadata to a flat list.
- */
- private void parse(boolean forFill) {
- Log.d(TAG, "Parsing structure for " + mStructure.getActivityComponent());
- int nodes = mStructure.getWindowNodeCount();
- mFilledAutofillFieldCollection = new FilledAutofillFieldCollection();
- for (int i = 0; i < nodes; i++) {
- WindowNode node = mStructure.getWindowNodeAt(i);
- ViewNode view = node.getRootViewNode();
- parseLocked(forFill, view);
- }
- }
-
- private void parseLocked(boolean forFill, ViewNode viewNode) {
- if (viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) {
- //TODO check to make sure hints are supported by service.
- if (forFill) {
- mAutofillFields.add(new AutofillFieldMetadata(viewNode));
- } else {
- mFilledAutofillFieldCollection.setAutofillValuesForHints
- (viewNode.getAutofillHints(), new FilledAutofillField(viewNode));
- }
- }
- int childrenSize = viewNode.getChildCount();
- if (childrenSize > 0) {
- for (int i = 0; i < childrenSize; i++) {
- parseLocked(forFill, viewNode.getChildAt(i));
- }
- }
- }
-
- public AutofillFieldMetadataCollection getAutofillFields() {
- return mAutofillFields;
- }
-
- public FilledAutofillFieldCollection getClientFormData() {
- return mFilledAutofillFieldCollection;
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.java
deleted file mode 100644
index 2296feb3a93422c70084d6820a3c6f8e6523e0dc..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice.datasource;
-
-import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection;
-
-import java.util.HashMap;
-import java.util.List;
-
-public interface AutofillRepository {
-
- /**
- * Gets saved FilledAutofillFieldCollection that contains some objects that can autofill fields with these
- * {@code autofillHints}.
- */
- HashMap getClientFormData(List focusedAutofillHints,
- List allAutofillHints);
-
- /**
- * Saves LoginCredential under this datasetName.
- */
- void saveClientFormData(FilledAutofillFieldCollection filledAutofillFieldCollection);
-
- /**
- * Clears all data.
- */
- void clear();
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.java
deleted file mode 100644
index 31f9200074d23716c6fa8fb071eade995a83bd9d..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice.datasource;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.ArraySet;
-
-import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection;
-import com.google.gson.Gson;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Singleton autofill data repository that stores autofill fields to SharedPreferences.
- * Disclaimer: you should not store sensitive fields like user data unencrypted. This is done
- * here only for simplicity and learning purposes.
- */
-public class SharedPrefsAutofillRepository implements AutofillRepository {
- private static final String SHARED_PREF_KEY = "com.example.android.autofillframework.service";
- private static final String CLIENT_FORM_DATA_KEY = "loginCredentialDatasets";
- private static final String DATASET_NUMBER_KEY = "datasetNumber";
-
- private static SharedPrefsAutofillRepository sInstance;
-
- private final SharedPreferences mPrefs;
-
- private SharedPrefsAutofillRepository(Context context) {
- mPrefs = context.getApplicationContext()
- .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE);
- }
-
- public static SharedPrefsAutofillRepository getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new SharedPrefsAutofillRepository(context);
- }
- return sInstance;
- }
-
- @Override
- public HashMap getClientFormData(List focusedAutofillHints,
- List allAutofillHints) {
- boolean hasDataForFocusedAutofillHints = false;
- HashMap clientFormDataMap = new HashMap<>();
- Set clientFormDataStringSet = getAllAutofillDataStringSet();
- for (String clientFormDataString : clientFormDataStringSet) {
- FilledAutofillFieldCollection filledAutofillFieldCollection = new Gson().fromJson(clientFormDataString, FilledAutofillFieldCollection.class);
- if (filledAutofillFieldCollection != null) {
- if (filledAutofillFieldCollection.helpsWithHints(focusedAutofillHints)) {
- // Saved data has data relevant to at least 1 of the hints associated with the
- // View in focus.
- hasDataForFocusedAutofillHints = true;
- }
- if (filledAutofillFieldCollection.helpsWithHints(allAutofillHints)) {
- // Saved data has data relevant to at least 1 of these hints associated with any
- // of the Views in the hierarchy.
- clientFormDataMap.put(filledAutofillFieldCollection.getDatasetName(), filledAutofillFieldCollection);
- }
- }
- }
- if (hasDataForFocusedAutofillHints) {
- return clientFormDataMap;
- } else {
- return null;
- }
- }
-
- @Override
- public void saveClientFormData(FilledAutofillFieldCollection filledAutofillFieldCollection) {
- String datasetName = "dataset-" + getDatasetNumber();
- filledAutofillFieldCollection.setDatasetName(datasetName);
- Set allAutofillData = getAllAutofillDataStringSet();
- allAutofillData.add(new Gson().toJson(filledAutofillFieldCollection));
- saveAllAutofillDataStringSet(allAutofillData);
- incrementDatasetNumber();
- }
-
- @Override
- public void clear() {
- mPrefs.edit().remove(CLIENT_FORM_DATA_KEY).apply();
- }
-
- private Set getAllAutofillDataStringSet() {
- return mPrefs.getStringSet(CLIENT_FORM_DATA_KEY, new ArraySet());
- }
-
- private void saveAllAutofillDataStringSet(Set allAutofillDataStringSet) {
- mPrefs.edit().putStringSet(CLIENT_FORM_DATA_KEY, allAutofillDataStringSet).apply();
- }
-
- /**
- * For simplicity, datasets will be named in the form "dataset-X" where X means
- * this was the Xth dataset saved.
- */
- private int getDatasetNumber() {
- return mPrefs.getInt(DATASET_NUMBER_KEY, 0);
- }
-
- /**
- * Every time a dataset is saved, this should be called to increment the dataset number.
- * (only important for this service's dataset naming scheme).
- */
- private void incrementDatasetNumber() {
- mPrefs.edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber() + 1).apply();
- }
-}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillField.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillField.java
deleted file mode 100644
index 3ca61d89ebdc616fc1669e7bcfdeb36aa06188ae..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillField.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice.model;
-
-import android.app.assist.AssistStructure;
-import android.view.autofill.AutofillValue;
-
-/**
- * JSON serializable data class containing the same data as an {@link AutofillValue}.
- */
-public class FilledAutofillField {
- private String mTextValue = null;
- private Long mDateValue = null;
- private Boolean mToggleValue = null;
-
- public FilledAutofillField(AssistStructure.ViewNode viewNode) {
- AutofillValue autofillValue = viewNode.getAutofillValue();
- if (autofillValue != null) {
- if (autofillValue.isList()) {
- CharSequence[] autofillOptions = viewNode.getAutofillOptions();
- int index = autofillValue.getListValue();
- if (autofillOptions != null && autofillOptions.length > 0) {
- mTextValue = autofillOptions[index].toString();
- }
- } else if (autofillValue.isDate()) {
- mDateValue = autofillValue.getDateValue();
- } else if (autofillValue.isText()) {
- // Using toString of AutofillValue.getTextValue in order to save it to
- // SharedPreferences.
- mTextValue = autofillValue.getTextValue().toString();
- }
- }
- }
-
- public String getTextValue() {
- return mTextValue;
- }
-
- public Long getDateValue() {
- return mDateValue;
- }
-
- public Boolean getToggleValue() {
- return mToggleValue;
- }
-
- public boolean isNull() {
- return mTextValue == null && mDateValue == null && mToggleValue == null;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- FilledAutofillField that = (FilledAutofillField) o;
-
- if (mTextValue != null ? !mTextValue.equals(that.mTextValue) : that.mTextValue != null)
- return false;
- if (mDateValue != null ? !mDateValue.equals(that.mDateValue) : that.mDateValue != null)
- return false;
- return mToggleValue != null ? mToggleValue.equals(that.mToggleValue) : that.mToggleValue == null;
- }
-
- @Override
- public int hashCode() {
- int result = mTextValue != null ? mTextValue.hashCode() : 0;
- result = 31 * result + (mDateValue != null ? mDateValue.hashCode() : 0);
- result = 31 * result + (mToggleValue != null ? mToggleValue.hashCode() : 0);
- return result;
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillFieldCollection.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillFieldCollection.java
deleted file mode 100644
index a7ff499251cb150d942061908dbef2d0706311f9..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillFieldCollection.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice.model;
-
-import android.service.autofill.Dataset;
-import android.support.annotation.NonNull;
-import android.util.Log;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-
-import com.example.android.autofillframework.multidatasetservice.AutofillFieldMetadata;
-import com.example.android.autofillframework.multidatasetservice.AutofillFieldMetadataCollection;
-
-import java.util.HashMap;
-import java.util.List;
-
-import static com.example.android.autofillframework.CommonUtil.TAG;
-
-/**
- * FilledAutofillFieldCollection is the model that holds all of the data on a client app's page,
- * plus the dataset name associated with it.
- */
-public final class FilledAutofillFieldCollection {
- private final HashMap mHintMap;
- private String mDatasetName;
-
- public FilledAutofillFieldCollection() {
- this(null, new HashMap());
- }
-
- public FilledAutofillFieldCollection(String datasetName, HashMap hintMap) {
- mHintMap = hintMap;
- mDatasetName = datasetName;
- }
-
- /**
- * Returns the name of the {@link Dataset}.
- */
- public String getDatasetName() {
- return mDatasetName;
- }
-
- /**
- * Sets the {@link Dataset} name.
- */
- public void setDatasetName(String datasetName) {
- mDatasetName = datasetName;
- }
-
- /**
- * Sets values for a list of hints.
- */
- public void setAutofillValuesForHints(@NonNull String[] autofillHints, @NonNull FilledAutofillField autofillValue) {
- for (int i = 0; i < autofillHints.length; i++) {
- mHintMap.put(autofillHints[i], autofillValue);
- }
- }
-
- /**
- * Populates a {@link Dataset.Builder} with appropriate values for each {@link AutofillId}
- * in a {@code AutofillFieldMetadataCollection}.
- */
- public boolean applyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection,
- Dataset.Builder datasetBuilder) {
- boolean setValueAtLeastOnce = false;
- List allHints = autofillFieldMetadataCollection.getAllHints();
- for (int hintIndex = 0; hintIndex < allHints.size(); hintIndex++) {
- String hint = allHints.get(hintIndex);
- List fillableAutofillFields = autofillFieldMetadataCollection.getFieldsForHint(hint);
- if (fillableAutofillFields == null) {
- continue;
- }
- for (int autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.size(); autofillFieldIndex++) {
- FilledAutofillField filledAutofillField = mHintMap.get(hint);
- if (filledAutofillField == null) {
- continue;
- }
- AutofillFieldMetadata autofillFieldMetadata = fillableAutofillFields.get(autofillFieldIndex);
- AutofillId autofillId = autofillFieldMetadata.getId();
- int autofillType = autofillFieldMetadata.getAutofillType();
- switch (autofillType) {
- case View.AUTOFILL_TYPE_LIST:
- int listValue = autofillFieldMetadata.getAutofillOptionIndex(filledAutofillField.getTextValue());
- if (listValue != -1) {
- datasetBuilder.setValue(autofillId, AutofillValue.forList(listValue));
- setValueAtLeastOnce = true;
- }
- break;
- case View.AUTOFILL_TYPE_DATE:
- Long dateValue = filledAutofillField.getDateValue();
- if (dateValue != null) {
- datasetBuilder.setValue(autofillId, AutofillValue.forDate(dateValue));
- setValueAtLeastOnce = true;
- }
- break;
- case View.AUTOFILL_TYPE_TEXT:
- String textValue = filledAutofillField.getTextValue();
- if (textValue != null) {
- datasetBuilder.setValue(autofillId, AutofillValue.forText(textValue));
- setValueAtLeastOnce = true;
- }
- break;
- case View.AUTOFILL_TYPE_TOGGLE:
- Boolean toggleValue = filledAutofillField.getToggleValue();
- if (toggleValue != null) {
- datasetBuilder.setValue(autofillId, AutofillValue.forToggle(toggleValue));
- setValueAtLeastOnce = true;
- }
- break;
- case View.AUTOFILL_TYPE_NONE:
- default:
- Log.w(TAG, "Invalid autofill type - " + autofillType);
- break;
- }
- }
- }
- return setValueAtLeastOnce;
- }
-
- public boolean helpsWithHints(List autofillHints) {
- for (int i = 0; i < autofillHints.size(); i++) {
- String autofillHint = autofillHints.get(i);
- if (mHintMap.get(autofillHint) != null && !mHintMap.get(autofillHint).isNull()) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.java
deleted file mode 100644
index f013f7852ef6bfeb062f0a2448ef3c781bf7b7dc..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2017 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.autofillframework.multidatasetservice.settings;
-
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.support.v7.app.AlertDialog;
-import android.support.v7.app.AppCompatActivity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.example.android.autofillframework.R;
-import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsAutofillRepository;
-
-public class SettingsActivity extends AppCompatActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.multidataset_service_settings_activity);
- final MyPreferences preferences = MyPreferences.getInstance(this);
- setupSettingsSwitch(R.id.settings_auth_responses_container,
- R.id.settings_auth_responses_label,
- R.id.settings_auth_responses_switch,
- preferences.isResponseAuth(),
- new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
- preferences.setResponseAuth(b);
- }
- });
- setupSettingsSwitch(R.id.settings_auth_datasets_container,
- R.id.settings_auth_datasets_label,
- R.id.settings_auth_datasets_switch,
- preferences.isDatasetAuth(),
- new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
- preferences.setDatasetAuth(b);
- }
- });
- setupSettingsButton(R.id.settings_clear_data_container,
- R.id.settings_clear_data_label,
- R.id.settings_clear_data_icon,
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- buildClearDataDialog().show();
- }
- });
- setupSettingsButton(R.id.settings_auth_credentials_container,
- R.id.settings_auth_credentials_label,
- R.id.settings_auth_credentials_icon,
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (preferences.getMasterPassword() != null) {
- buildCurrentCredentialsDialog().show();
- } else {
- buildNewCredentialsDialog().show();
- }
- }
- });
- }
-
- private AlertDialog buildClearDataDialog() {
- return new AlertDialog.Builder(SettingsActivity.this)
- .setMessage(R.string.settings_clear_data_confirmation)
- .setTitle(R.string.settings_clear_data_confirmation_title)
- .setNegativeButton(R.string.cancel, null)
- .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- SharedPrefsAutofillRepository.getInstance
- (SettingsActivity.this).clear();
- MyPreferences.getInstance(SettingsActivity.this)
- .clearCredentials();
- dialog.dismiss();
- }
- })
- .create();
- }
-
- private AlertDialog.Builder prepareCredentialsDialog() {
- return new AlertDialog.Builder(SettingsActivity.this)
- .setTitle(R.string.settings_auth_change_credentials_title)
- .setNegativeButton(R.string.cancel, null);
- }
-
- private AlertDialog buildCurrentCredentialsDialog() {
- final EditText currentPasswordField = LayoutInflater
- .from(SettingsActivity.this)
- .inflate(R.layout.multidataset_service_settings_authentication_dialog, null)
- .findViewById(R.id.master_password_field);
- return prepareCredentialsDialog()
- .setMessage(R.string.settings_auth_enter_current_password)
- .setView(currentPasswordField)
- .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String password = currentPasswordField.getText().toString();
- if (MyPreferences.getInstance(SettingsActivity.this).getMasterPassword()
- .equals(password)) {
- buildNewCredentialsDialog().show();
- dialog.dismiss();
- }
- }
- })
- .create();
- }
-
- private AlertDialog buildNewCredentialsDialog() {
- final EditText newPasswordField = LayoutInflater
- .from(SettingsActivity.this)
- .inflate(R.layout.multidataset_service_settings_authentication_dialog, null)
- .findViewById(R.id.master_password_field);
- return prepareCredentialsDialog()
- .setMessage(R.string.settings_auth_enter_new_password)
- .setView(newPasswordField)
- .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String password = newPasswordField.getText().toString();
- MyPreferences.getInstance(SettingsActivity.this).setMasterPassword(password);
- dialog.dismiss();
- }
- })
- .create();
- }
-
- private void setupSettingsSwitch(int containerId, int labelId, int switchId, boolean checked,
- CompoundButton.OnCheckedChangeListener checkedChangeListener) {
- ViewGroup container = (ViewGroup) findViewById(containerId);
- String switchLabel = ((TextView) container.findViewById(labelId)).getText().toString();
- final Switch switchView = container.findViewById(switchId);
- switchView.setContentDescription(switchLabel);
- switchView.setChecked(checked);
- container.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- switchView.performClick();
- }
- });
- switchView.setOnCheckedChangeListener(checkedChangeListener);
- }
-
- private void setupSettingsButton(int containerId, int labelId, int imageViewId,
- final View.OnClickListener onClickListener) {
- ViewGroup container = (ViewGroup) findViewById(containerId);
- String buttonLabel = ((TextView) container.findViewById(labelId)).getText().toString();
- final ImageView imageView = container.findViewById(imageViewId);
- imageView.setContentDescription(buttonLabel);
- container.setOnClickListener(onClickListener);
- }
-}
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable-hdpi/ic_launcher.png b/input/autofill/AutofillFramework/Application/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..b1efaf4b23929755615dc16fa196c5f8496bbf9a
Binary files /dev/null and b/input/autofill/AutofillFramework/Application/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable-hdpi/tile.9.png b/input/autofill/AutofillFramework/Application/src/main/res/drawable-hdpi/tile.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..135862883e26eddce2b19db021adf62e10357ad0
Binary files /dev/null and b/input/autofill/AutofillFramework/Application/src/main/res/drawable-hdpi/tile.9.png differ
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable-mdpi/ic_launcher.png b/input/autofill/AutofillFramework/Application/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5f9244f24dccccab7dbc1259ddfceb0cba619c0
Binary files /dev/null and b/input/autofill/AutofillFramework/Application/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable-xhdpi/ic_launcher.png b/input/autofill/AutofillFramework/Application/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d07b3f06606f5f005afa8237a53f7ff3ef5966e
Binary files /dev/null and b/input/autofill/AutofillFramework/Application/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable-xxhdpi/ic_launcher.png b/input/autofill/AutofillFramework/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..6ef21e1f4dfe8eeec63fde13a453812511646c83
Binary files /dev/null and b/input/autofill/AutofillFramework/Application/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_delete_forever_black_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_autocomplete_logo_24dp.xml
similarity index 56%
rename from input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_delete_forever_black_24dp.xml
rename to input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_autocomplete_logo_24dp.xml
index 4d2afb01d50eb42f17352bd4217bd0e06e2d36c4..18dc280b8ec88438d1da53694b77206f16e3caab 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_delete_forever_black_24dp.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_autocomplete_logo_24dp.xml
@@ -13,8 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
-->
-
-
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_custom_virtual_logo_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_custom_virtual_logo_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f230e2680b09f811c40a54fab979d24290f149fd
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_custom_virtual_logo_24dp.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_disabled_black_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_disabled_black_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..60e4cbd38f40d5f274de2003b4a76e09063b18bb
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_disabled_black_24dp.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_edittexts_logo_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_edittexts_logo_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b260df239ec46b7b1047ac10197b33013663e4f6
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_edittexts_logo_24dp.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_email_black_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_email_black_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2c31d6861f49859496afe3ef3fbf3bc57ce262bc
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_email_black_24dp.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_info_black_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_info_black_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a3dba1d9fe18d5ee2105317ba40f1a38873010be
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_info_black_24dp.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_person_black_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_person_black_24dp.xml
index 6534d9f0ec4a2ecb5fee31815134e0202cc5b171..032db1299bd28b612630c3fac06e48c94f48a2d1 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_person_black_24dp.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_person_black_24dp.xml
@@ -13,8 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
-->
-
-
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_send_white_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_send_white_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5e92b26679cf275e43675012fc26635b9a8200d2
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_send_white_24dp.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_spinners_logo_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_spinners_logo_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ea91d03150acf9eb35af6e3342a2a4c6484b2d20
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_spinners_logo_24dp.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_view_module_black_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_view_module_black_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bb739bb31c18c0caea582a116fa07433b8933e9f
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_view_module_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_web_black_24dp.xml b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_web_black_24dp.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d5e35bfd035b29ab912444c2f1b04b6cc828213d
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/drawable/ic_web_black_24dp.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/activity_main.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/activity_main.xml
index b2510b07bec374761a481bcf776fa44af076347a..ece13e9d03ae7c19284f77a04fd007d829e98348 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/activity_main.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/activity_main.xml
@@ -1,5 +1,4 @@
-
-
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
-
+ app:tabMode="fixed" />
-
-
-
-
-
-
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/cc_exp_date.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/cc_exp_date.xml
new file mode 100644
index 0000000000000000000000000000000000000000..aebe9f833082345a111b4560288792a3f6fac9cd
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/cc_exp_date.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_activity.xml
index 7d8e099fe7242028f6724578fb9daa51b83deece..414803a4a7821ebb5dced69e7cd613540b578a28 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_activity.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_activity.xml
@@ -1,5 +1,4 @@
-
-
-
+
-
+
+
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:background="@drawable/ic_info_black_24dp"
+ app:dialogText="@string/anti_pattern_credit_card_info"
+ app:layout_constraintBottom_toBottomOf="@+id/creditCardHeader"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toEndOf="@+id/creditCardHeader"
+ app:layout_constraintTop_toTopOf="@+id/creditCardHeader" />
-
+
-
+
-
+
+
+
-
+
-
+
-
+
-
-
+
-
+
-
+ android:layout_alignBaseline="@+id/creditCardSecurityCodeLabel"
+ android:layout_marginEnd="8dp"
+ android:autofillHints="creditCardSecurityCode"
+ android:inputType="number"
+ app:layout_constraintBottom_toBottomOf="@+id/creditCardSecurityCodeLabel"
+ app:layout_constraintEnd_toEndOf="@+id/expirationYear"
+ app:layout_constraintStart_toStartOf="@+id/expirationYear"
+ app:layout_constraintTop_toTopOf="@+id/creditCardSecurityCodeLabel" />
-
+
-
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_anti_pattern_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_anti_pattern_activity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9a3d4ae339119f71e3fffc06884413d7458b5089
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_anti_pattern_activity.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_compound_view_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_compound_view_activity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1e00f26397dc224a08a9e99410b0221f05e29bdf
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_compound_view_activity.xml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_date_picker_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_date_picker_activity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ace95d4e5acefe625f062e38ade371d3beb7898f
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_date_picker_activity.xml
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_spinners_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_spinners_activity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6c1bfef45c21c1ce226c458c77cf502e449ea110
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/credit_card_spinners_activity.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/email_compose_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/email_compose_activity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bd1bd435cd30f16e4b760892b7d98ca37c90eaf3
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/email_compose_activity.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_common_cases.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_common_cases.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2d8078ebd8bb5b35dccef9fdfd54745b0486cf2e
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_common_cases.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000000000000000000000000000000000000..ab7c32aa623f007b851dc336e296bcbdbb19e2c4
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/fragment_edge_cases.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/login_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/login_activity.xml
index 6382fe7570ace830c47b12cd1f49643a4fb1035f..1cb4232a42635d185c2b85f7e5a7b662498b6536 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/login_activity.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/login_activity.xml
@@ -1,5 +1,4 @@
-
-
-
+
-
+ android:layout_marginTop="8dp"
+ android:gravity="center"
+ android:text="@string/navigation_button_edittext_login_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" />
-
+
-
+
-
+
-
-
+
-
+ android:layout_marginEnd="@dimen/spacing_normal"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:layout_marginTop="@dimen/spacing_normal"
+ android:autofillHints="password"
+ 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" />
-
+
-
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/login_webview_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/login_webview_activity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..abbd1f462d5b90bdf1b29cbaa792109582792dc9
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/login_webview_activity.xml
@@ -0,0 +1,19 @@
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/login_with_autocomplete_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/login_with_autocomplete_activity.xml
index 193387b8f9c0a2b3b66313045d44bc5ce72e7762..d9f89e7df2e587562d0fda951215a7944030b133 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/login_with_autocomplete_activity.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/login_with_autocomplete_activity.xml
@@ -1,5 +1,4 @@
-
-
-
+
-
+ android:layout_marginTop="8dp"
+ android:gravity="center"
+ android:text="@string/navigation_button_autocomplete_login_label"
+ 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" />
-
+
-
+
-
+
-
-
+
-
+ android:layout_marginEnd="@dimen/spacing_normal"
+ android:layout_marginStart="@dimen/spacing_normal"
+ android:layout_marginTop="@dimen/spacing_normal"
+ android:autofillHints="password"
+ 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" />
-
+
-
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/multidataset_service_auth_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/multidataset_service_auth_activity.xml
deleted file mode 100644
index 0890af8696766ce9ddbcf443bd48800bde050877..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/multidataset_service_auth_activity.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/multidataset_service_settings_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/multidataset_service_settings_activity.xml
deleted file mode 100644
index 3704e40fe7a8e60024d83b9c1f3082288f229bdb..0000000000000000000000000000000000000000
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/multidataset_service_settings_activity.xml
+++ /dev/null
@@ -1,143 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/multiple_partitions_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/multiple_partitions_activity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e0fae46070b0a3b5d53684863368b8f140fd6be3
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/multiple_partitions_activity.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/multiple_steps_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/multiple_steps_activity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fe7fd098da897ffad2fcd4e4af5888d620365250
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/multiple_steps_activity.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/navigation_button.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/navigation_button.xml
new file mode 100644
index 0000000000000000000000000000000000000000..84e27be448d913469aedd9c77c22c77f5a5bef6a
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/navigation_button.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/navigation_item.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/navigation_item.xml
new file mode 100644
index 0000000000000000000000000000000000000000..200ab16b0095235d0e329f3a87adad23a094ab23
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/navigation_item.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/virtual_login_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/virtual_login_activity.xml
index 59f56e1d405b1ec8ed347642a4f726852a644c6a..31ca52f2c874cc8573f9be28f47506d4304aadb1 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/virtual_login_activity.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/virtual_login_activity.xml
@@ -1,5 +1,4 @@
-
-
-
+
-
+
+
+
+
+ android:layout_height="@dimen/custom_view_height"
+ android:layout_marginEnd="8dp"
+ android:layout_marginStart="8dp"
+ android:paddingEnd="@dimen/spacing_large"
+ android:paddingStart="@dimen/spacing_large"
+ android:paddingTop="@dimen/spacing_large"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/custom_virtual_login_header" />
-
+ 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/custom_view" />
-
-
-
-
-
+
+
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/welcome_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/welcome_activity.xml
index 4d746c53d16988e3440c7ab0401155d0317ed455..a400a84c8e785a22a524c89d6e5b3da87b44407b 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/layout/welcome_activity.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/welcome_activity.xml
@@ -1,5 +1,4 @@
-
-
-
+
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/raw/sample_form.html b/input/autofill/AutofillFramework/Application/src/main/res/raw/sample_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..92596a5968d8c9a6fb71da16a285963d4bced9ee
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/raw/sample_form.html
@@ -0,0 +1,65 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/values/attrs.xml b/input/autofill/AutofillFramework/Application/src/main/res/values/attrs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bfadd1a1a71f35ef0812d825da30d0b69c5449ea
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/res/values/attrs.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/values/dimens.xml b/input/autofill/AutofillFramework/Application/src/main/res/values/dimens.xml
index 961725d52038a3d79f8cafe6804677d40bbda7f4..965af8a03743e044683803925b65fc92a8bbcbc9 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/values/dimens.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/values/dimens.xml
@@ -1,5 +1,4 @@
-
-
+
+
+
+
+
+
+ Credentials
+ Credit Card
+
+ Autofilled partition \'%1$s\'
+ No partition for id %1$d on %2$s
+ Blocked cross-partitions: %1$s
+ Ignoring autofill on read-only field %1$s
+ INVALID
+ Representing expiration dates as %1$s
+
+ Finished
+ Showing step %1$d
+ %1$s: %2$s
+
+ Prev
+ Next
+ Finish
+
+
+ - Automatically return to main page in %d second.
+ - Automatically return to main page in %d seconds.
+
+
+
+ - user-1
+ - user-2
+
+
- - Jan
- - Feb
- - Mar
- - Apr
- - May
- - Jun
- - Jul
- - Aug
- - Sep
- - Oct
- - Nov
- - Dec
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5
+ - 6
+ - 7
+ - 8
+ - 9
+ - 10
+ - 11
+ - 12
@@ -90,25 +199,6 @@
- 25
- 26
- 27
- - 28
- - 30
- - 31
-
-
-
- - 2017
- - 2018
- - 2019
- - 2020
- - 2021
- - 2022
- - 2023
- - 2024
-
-
-
- - user-1
- - user-2
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 92aabaa6ebba5a351adfea043551ea9c1f65b2b8..39e5ba88e4c8d6ee2c8f77410651b41e93469c81 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/values/styles.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/values/styles.xml
@@ -1,5 +1,4 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/.gitignore b/input/autofill/AutofillFramework/afservice/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/input/autofill/AutofillFramework/afservice/build.gradle b/input/autofill/AutofillFramework/afservice/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f61420770357e38858a08ae45f2edb977fd31455
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/build.gradle
@@ -0,0 +1,51 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion "android-P"
+
+ defaultConfig {
+ minSdkVersion 26
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ debug {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation 'com.android.support:appcompat-v7:28.0.0-alpha1'
+ implementation "android.arch.persistence.room:runtime:1.0.0"
+ annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'com.android.support:design:28.0.0-alpha1'
+ implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+ implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.2'
+ implementation 'com.squareup.retrofit2:retrofit:2.3.0'
+ implementation group: 'com.google.guava', name: 'guava', version: '22.0-android'
+ implementation "com.android.support.test.espresso:espresso-idling-resource:3.0.1"
+ implementation "com.google.code.findbugs:jsr305:3.0.2"
+
+ androidTestImplementation "junit:junit:4.12"
+ androidTestImplementation ("com.android.support.test.espresso:espresso-core:3.0.1")
+ androidTestImplementation "com.android.support.test.espresso:espresso-contrib:3.0.1"
+ androidTestImplementation "com.android.support.test.espresso:espresso-intents:3.0.1"
+ androidTestImplementation "com.android.support.test.espresso.idling:idling-concurrent:3.0.1"
+
+}
diff --git a/input/autofill/AutofillFramework/afservice/proguard-rules.pro b/input/autofill/AutofillFramework/afservice/proguard-rules.pro
new file mode 100644
index 0000000000000000000000000000000000000000..a313042ca3a7198bccdb25e7c101c6b2211afee9
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/proguard-rules.pro
@@ -0,0 +1,24 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+-keep class com.google.common.base.Preconditions { *; }
+-keep class android.arch.** { *; }
+-keep com.example.android.autofill.service.** { *; }
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/AutofillDaoTest.java b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/AutofillDaoTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c76ba435b64e5f8d505d51bca9cd4b1edf7960af
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/AutofillDaoTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017, 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.data.source.local;
+
+import android.arch.persistence.room.Room;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasSize;
+
+@RunWith(AndroidJUnit4.class)
+public class AutofillDaoTest {
+ private final AutofillDataset mDataset =
+ new AutofillDataset(UUID.randomUUID().toString(),
+ "dataset-1", InstrumentationRegistry.getContext().getPackageName());
+ private final FilledAutofillField mUsernameField =
+ new FilledAutofillField(mDataset.getId(), View.AUTOFILL_HINT_USERNAME, "login");
+ private final FilledAutofillField mPasswordField =
+ new FilledAutofillField(mDataset.getId(), View.AUTOFILL_HINT_PASSWORD, "password");
+
+ private AutofillDatabase mDatabase;
+
+ @Before
+ public void setup() {
+ // using an in-memory database because the information stored here disappears when the
+ // process is killed
+ mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+ AutofillDatabase.class).build();
+
+ }
+
+ @After
+ public void closeDb() {
+ mDatabase.close();
+ }
+
+ @Test
+ public void insertFilledAutofillFieldAndGet() {
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
+ new DatasetWithFilledAutofillFields();
+ datasetWithFilledAutofillFields.autofillDataset = mDataset;
+ datasetWithFilledAutofillFields.filledAutofillFields =
+ Arrays.asList(mUsernameField, mPasswordField);
+ datasetWithFilledAutofillFields.filledAutofillFields
+ .sort(Comparator.comparing(FilledAutofillField::getFieldTypeName));
+
+ // When inserting a page's autofill fields.
+ mDatabase.autofillDao().insertAutofillDataset(mDataset);
+ mDatabase.autofillDao().insertFilledAutofillFields(
+ datasetWithFilledAutofillFields.filledAutofillFields);
+
+ // Represents all hints of all fields on page.
+ List allHints = ImmutableList.of(View.AUTOFILL_HINT_USERNAME,
+ View.AUTOFILL_HINT_PASSWORD);
+ List loadedDatasets = mDatabase.autofillDao()
+ .getDatasets(allHints);
+ loadedDatasets.get(0).filledAutofillFields.sort(
+ Comparator.comparing(FilledAutofillField::getFieldTypeName));
+ assertThat(loadedDatasets, contains(datasetWithFilledAutofillFields));
+ assertThat(loadedDatasets, hasSize(1));
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f445c541f33e965f25c34b0c1d8cf0b1abbfca5
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.data.source.local;
+
+import android.arch.persistence.room.Room;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.util.SingleExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class LocalDataSourceTest {
+
+ private LocalAutofillDataSource mLocalDataSource;
+ private AutofillDatabase mDatabase;
+
+ @Before
+ public void setup() {
+ // using an in-memory database for testing, since it doesn't survive killing the process
+ mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+ AutofillDatabase.class)
+ .build();
+ AutofillDao tasksDao = mDatabase.autofillDao();
+ SharedPreferences sharedPreferences = InstrumentationRegistry.getContext()
+ .getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+ // Make sure that we're not keeping a reference to the wrong instance.
+ LocalAutofillDataSource.clearInstance();
+ mLocalDataSource = LocalAutofillDataSource.getInstance(sharedPreferences,
+ tasksDao, new SingleExecutors());
+ }
+
+ @After
+ public void cleanUp() {
+ try {
+ mDatabase.close();
+ } finally {
+ LocalAutofillDataSource.clearInstance();
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java
new file mode 100644
index 0000000000000000000000000000000000000000..baac2b1962e6702207e1190097d162da0f775d8d
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Allow instant execution of tasks.
+ */
+public final class SingleExecutors extends AppExecutors {
+ private static Executor sInstance = Runnable::run;
+
+ public SingleExecutors() {
+ super(sInstance, sInstance, sInstance);
+ }
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b02a591d5e1369e7247a88eb764816869c6c5f8a
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AuthActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AuthActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1660b7c43bc53a118d09f826feaa43d3af0a30e
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AuthActivity.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Editable;
+import android.widget.EditText;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.example.android.autofill.service.data.ClientViewMetadata;
+import com.example.android.autofill.service.data.ClientViewMetadataBuilder;
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.adapter.DatasetAdapter;
+import com.example.android.autofill.service.data.adapter.ResponseAdapter;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
+import com.example.android.autofill.service.data.source.local.DigitalAssetLinksRepository;
+import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.settings.MyPreferences;
+import com.example.android.autofill.service.util.AppExecutors;
+import com.google.gson.GsonBuilder;
+
+import java.util.HashMap;
+import java.util.List;
+
+import static android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE;
+import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT;
+import static com.example.android.autofill.service.util.Util.EXTRA_DATASET_NAME;
+import static com.example.android.autofill.service.util.Util.EXTRA_FOR_RESPONSE;
+import static com.example.android.autofill.service.util.Util.logw;
+
+
+/**
+ * This Activity controls the UI for logging in to the Autofill service.
+ * It is launched when an Autofill Response or specific Dataset within the Response requires
+ * authentication to access. It bundles the result in an Intent.
+ */
+public class AuthActivity extends AppCompatActivity {
+
+ // Unique id for dataset intents.
+ private static int sDatasetPendingIntentId = 0;
+
+ private LocalAutofillDataSource mLocalAutofillDataSource;
+ private DigitalAssetLinksRepository mDalRepository;
+ private EditText mMasterPassword;
+ private DatasetAdapter mDatasetAdapter;
+ private ResponseAdapter mResponseAdapter;
+ private ClientViewMetadata mClientViewMetadata;
+ private String mPackageName;
+ private Intent mReplyIntent;
+ private MyPreferences mPreferences;
+
+ public static IntentSender getAuthIntentSenderForResponse(Context context) {
+ final Intent intent = new Intent(context, AuthActivity.class);
+ return PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+ }
+
+ public static IntentSender getAuthIntentSenderForDataset(Context originContext,
+ String datasetName) {
+ Intent intent = new Intent(originContext, AuthActivity.class);
+ intent.putExtra(EXTRA_DATASET_NAME, datasetName);
+ intent.putExtra(EXTRA_FOR_RESPONSE, false);
+ return PendingIntent.getActivity(originContext, ++sDatasetPendingIntentId, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.multidataset_service_auth_activity);
+ SharedPreferences sharedPreferences =
+ getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+ DefaultFieldTypesSource defaultFieldTypesSource =
+ DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
+ new GsonBuilder().create());
+ AutofillDao autofillDao = AutofillDatabase.getInstance(this,
+ defaultFieldTypesSource, new AppExecutors()).autofillDao();
+ mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(sharedPreferences,
+ autofillDao, new AppExecutors());
+ mDalRepository = DigitalAssetLinksRepository.getInstance(getPackageManager());
+ mMasterPassword = findViewById(R.id.master_password);
+ mPackageName = getPackageName();
+ mPreferences = MyPreferences.getInstance(this);
+ findViewById(R.id.login).setOnClickListener((view) -> login());
+ findViewById(R.id.cancel).setOnClickListener((view) -> {
+ onFailure();
+ AuthActivity.this.finish();
+ });
+ }
+
+ private void login() {
+ Editable password = mMasterPassword.getText();
+ String correctPassword = MyPreferences.getInstance(AuthActivity.this).getMasterPassword();
+ if (password.toString().equals(correctPassword)) {
+ onSuccess();
+ } else {
+ Toast.makeText(this, "Password incorrect", Toast.LENGTH_SHORT).show();
+ onFailure();
+ }
+ }
+
+ @Override
+ public void finish() {
+ if (mReplyIntent != null) {
+ setResult(RESULT_OK, mReplyIntent);
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+ super.finish();
+ }
+
+ private void onFailure() {
+ logw("Failed auth.");
+ mReplyIntent = null;
+ }
+
+ private void onSuccess() {
+ Intent intent = getIntent();
+ boolean forResponse = intent.getBooleanExtra(EXTRA_FOR_RESPONSE, true);
+ AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
+ ClientParser clientParser = new ClientParser(structure);
+ mReplyIntent = new Intent();
+ mLocalAutofillDataSource.getFieldTypeByAutofillHints(
+ new DataCallback>() {
+ @Override
+ public void onLoaded(HashMap fieldTypesByAutofillHint) {
+ ClientViewMetadataBuilder builder = new ClientViewMetadataBuilder(clientParser,
+ fieldTypesByAutofillHint);
+ mClientViewMetadata = builder.buildClientViewMetadata();
+ mDatasetAdapter = new DatasetAdapter(clientParser);
+ mResponseAdapter = new ResponseAdapter(AuthActivity.this,
+ mClientViewMetadata, mPackageName, mDatasetAdapter);
+ if (forResponse) {
+ fetchAllDatasetsAndSetIntent(fieldTypesByAutofillHint);
+ } else {
+ String datasetName = intent.getStringExtra(EXTRA_DATASET_NAME);
+ fetchDatasetAndSetIntent(fieldTypesByAutofillHint, datasetName);
+ }
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+
+ }
+ });
+ }
+
+ private void fetchDatasetAndSetIntent(
+ HashMap fieldTypesByAutofillHint, String datasetName) {
+ mLocalAutofillDataSource.getAutofillDataset(mClientViewMetadata.getAllHints(),
+ datasetName, new DataCallback() {
+ @Override
+ public void onLoaded(DatasetWithFilledAutofillFields dataset) {
+ String datasetName = dataset.autofillDataset.getDatasetName();
+ RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+ mPackageName, datasetName);
+ setDatasetIntent(mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
+ dataset, remoteViews));
+ finish();
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+ logw(msg, params);
+ finish();
+ }
+ });
+ }
+
+ private void fetchAllDatasetsAndSetIntent(
+ HashMap fieldTypesByAutofillHint) {
+ mLocalAutofillDataSource.getAutofillDatasets(mClientViewMetadata.getAllHints(),
+ new DataCallback>() {
+ @Override
+ public void onLoaded(List datasets) {
+ boolean datasetAuth = mPreferences.isDatasetAuth();
+ FillResponse fillResponse = mResponseAdapter.buildResponse(
+ fieldTypesByAutofillHint, datasets, datasetAuth);
+ setResponseIntent(fillResponse);
+ finish();
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+ logw(msg, params);
+ finish();
+ }
+ });
+ }
+
+ private void setResponseIntent(FillResponse fillResponse) {
+ mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse);
+ }
+
+ private void setDatasetIntent(Dataset dataset) {
+ mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset);
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHintProperties.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHintProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..8314eb304cda6aea22b2b35d2870b0cb337b6aec
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHintProperties.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.view.View;
+
+import com.example.android.autofill.service.model.FilledAutofillField;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Holds the properties associated with an autofill hint in this Autofill Service.
+ */
+public final class AutofillHintProperties {
+
+ private String mAutofillHint;
+ private FakeFieldGenerator mFakeFieldGenerator;
+ private Set mValidTypes;
+ private int mSaveType;
+ private int mPartition;
+
+ public AutofillHintProperties(String autofillHint, int saveType, int partitionNumber,
+ FakeFieldGenerator fakeFieldGenerator, Integer... validTypes) {
+ mAutofillHint = autofillHint;
+ mSaveType = saveType;
+ mPartition = partitionNumber;
+ mFakeFieldGenerator = fakeFieldGenerator;
+ mValidTypes = new HashSet<>(Arrays.asList(validTypes));
+ }
+
+ /**
+ * Generates dummy autofill field data that is relevant to the autofill hint.
+ */
+ public FilledAutofillField generateFakeField(int seed, String datasetId) {
+ return mFakeFieldGenerator.generate(seed, datasetId);
+ }
+
+ /**
+ * Returns autofill hint associated with these properties. If you save a field that uses a W3C
+ * hint, there is a chance this will return a different but analogous hint, when applicable.
+ * For example, W3C has hint 'email' and {@link android.view.View} has hint 'emailAddress', so
+ * the W3C hint should map to the hint defined in {@link android.view.View} ('emailAddress').
+ */
+ public String getAutofillHint() {
+ return mAutofillHint;
+ }
+
+ /**
+ * Returns how this hint maps to a {@link android.service.autofill.SaveInfo} type.
+ */
+ public int getSaveType() {
+ return mSaveType;
+ }
+
+ /**
+ * Returns which data partition this autofill hint should be a part of. See partitions defined
+ * in {@link AutofillHints}.
+ */
+ public int getPartition() {
+ return mPartition;
+ }
+
+
+ /**
+ * Sometimes, data for a hint should only be stored as a certain AutofillValue type. For
+ * example, it is recommended that data representing a Credit Card Expiration date, annotated
+ * with the hint {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}, should
+ * only be stored as {@link View#AUTOFILL_TYPE_DATE}.
+ */
+ public boolean isValidType(int type) {
+ return mValidTypes.contains(type);
+ }
+
+ public Set getTypes() {
+ return mValidTypes;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHints.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHints.java
new file mode 100644
index 0000000000000000000000000000000000000000..d43071f203a8ebb1750ba456b9eadc92f296a687
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHints.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.support.annotation.NonNull;
+
+import com.example.android.autofill.service.model.FakeData;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+
+import static com.example.android.autofill.service.util.Util.logd;
+import static com.example.android.autofill.service.util.Util.logw;
+import static java.util.stream.Collectors.toList;
+
+public final class AutofillHints {
+ public static final int PARTITION_ALL = -1;
+ public static final int PARTITION_OTHER = 0;
+ public static final int PARTITION_ADDRESS = 1;
+ public static final int PARTITION_EMAIL = 2;
+ public static final int PARTITION_CREDIT_CARD = 3;
+ public static final int[] PARTITIONS = {
+ PARTITION_OTHER, PARTITION_ADDRESS, PARTITION_EMAIL, PARTITION_CREDIT_CARD
+ };
+
+ private AutofillHints() {
+ }
+
+ public static FilledAutofillField generateFakeField(
+ FieldTypeWithHeuristics fieldTypeWithHeuristics, String packageName, int seed,
+ String datasetId) {
+ FakeData fakeData = fieldTypeWithHeuristics.fieldType.getFakeData();
+ String fieldTypeName = fieldTypeWithHeuristics.fieldType.getTypeName();
+ String text = null;
+ Long date = null;
+ Boolean toggle = null;
+ if (fakeData.strictExampleSet != null && fakeData.strictExampleSet.strings != null &&
+ fakeData.strictExampleSet.strings.size() > 0 &&
+ !fakeData.strictExampleSet.strings.get(0).isEmpty()) {
+ List choices = fakeData.strictExampleSet.strings;
+ text = choices.get(seed % choices.size());
+ } else if (fakeData.textTemplate != null) {
+ text = fakeData.textTemplate.replace("seed", "" + seed)
+ .replace("curr_time", "" + Calendar.getInstance().getTimeInMillis());
+ } else if (fakeData.dateTemplate != null) {
+ if (fakeData.dateTemplate.contains("curr_time")) {
+ date = Calendar.getInstance().getTimeInMillis();
+ }
+ }
+ return new FilledAutofillField(datasetId, fieldTypeName, text, date, toggle);
+ }
+
+ public static String getFieldTypeNameFromAutofillHints(
+ HashMap fieldTypesByAutofillHint,
+ @NonNull List hints) {
+ return getFieldTypeNameFromAutofillHints(fieldTypesByAutofillHint, hints, PARTITION_ALL);
+ }
+
+ public static String getFieldTypeNameFromAutofillHints(
+ HashMap fieldTypesByAutofillHint,
+ @NonNull List hints, int partition) {
+ List fieldTypeNames = removePrefixes(hints)
+ .stream()
+ .filter(fieldTypesByAutofillHint::containsKey)
+ .map(fieldTypesByAutofillHint::get)
+ .filter(Objects::nonNull)
+ .filter((fieldTypeWithHints) ->
+ matchesPartition(fieldTypeWithHints.fieldType.getPartition(), partition))
+ .map(FieldTypeWithHeuristics::getFieldType).map(FieldType::getTypeName)
+ .collect(toList());
+ if (fieldTypeNames != null && fieldTypeNames.size() > 0) {
+ return fieldTypeNames.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ public static boolean matchesPartition(int partition, int otherPartition) {
+ return partition == PARTITION_ALL || otherPartition == PARTITION_ALL ||
+ partition == otherPartition;
+ }
+
+ private static List removePrefixes(@NonNull List hints) {
+ List hintsWithoutPrefixes = new ArrayList<>();
+ String nextHint = null;
+ for (int i = 0; i < hints.size(); i++) {
+ String hint = hints.get(i);
+ if (i < hints.size() - 1) {
+ nextHint = hints.get(i + 1);
+ }
+ // First convert the compound W3C autofill hints
+ if (isW3cSectionPrefix(hint) && i < hints.size() - 1) {
+ i++;
+ hint = hints.get(i);
+ logd("Hint is a W3C section prefix; using %s instead", hint);
+ if (i < hints.size() - 1) {
+ nextHint = hints.get(i + 1);
+ }
+ }
+ if (isW3cTypePrefix(hint) && nextHint != null && isW3cTypeHint(nextHint)) {
+ hint = nextHint;
+ i++;
+ logd("Hint is a W3C type prefix; using %s instead", hint);
+ }
+ if (isW3cAddressType(hint) && nextHint != null) {
+ hint = nextHint;
+ i++;
+ logd("Hint is a W3C address prefix; using %s instead", hint);
+ }
+ hintsWithoutPrefixes.add(hint);
+ }
+ return hintsWithoutPrefixes;
+ }
+
+ private static boolean isW3cSectionPrefix(@NonNull String hint) {
+ return hint.startsWith(W3cHints.PREFIX_SECTION);
+ }
+
+ private static boolean isW3cAddressType(@NonNull String hint) {
+ switch (hint) {
+ case W3cHints.SHIPPING:
+ case W3cHints.BILLING:
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isW3cTypePrefix(@NonNull String hint) {
+ switch (hint) {
+ case W3cHints.PREFIX_WORK:
+ case W3cHints.PREFIX_FAX:
+ case W3cHints.PREFIX_HOME:
+ case W3cHints.PREFIX_PAGER:
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isW3cTypeHint(@NonNull String hint) {
+ switch (hint) {
+ case W3cHints.TEL:
+ case W3cHints.TEL_COUNTRY_CODE:
+ case W3cHints.TEL_NATIONAL:
+ case W3cHints.TEL_AREA_CODE:
+ case W3cHints.TEL_LOCAL:
+ case W3cHints.TEL_LOCAL_PREFIX:
+ case W3cHints.TEL_LOCAL_SUFFIX:
+ case W3cHints.TEL_EXTENSION:
+ case W3cHints.EMAIL:
+ case W3cHints.IMPP:
+ return true;
+ }
+ logw("Invalid W3C type hint: %s", hint);
+ return false;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ClientParser.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ClientParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..048c0018efd2060d6d3c309397334f1b9ca57ee4
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ClientParser.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.assist.AssistStructure;
+import android.support.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+import static android.app.assist.AssistStructure.ViewNode;
+
+/**
+ * Wrapper for {@link AssistStructure} to make it easy to parse.
+ */
+public final class ClientParser {
+ private final List mStructures;
+
+ public ClientParser(@NonNull List structures) {
+ Preconditions.checkNotNull(structures);
+ mStructures = structures;
+ }
+
+ public ClientParser(@NonNull AssistStructure structure) {
+ this(ImmutableList.of(structure));
+ }
+
+ /**
+ * Traverses through the {@link AssistStructure} and does something at each {@link ViewNode}.
+ *
+ * @param processor contains action to be performed on each {@link ViewNode}.
+ */
+ public void parse(NodeProcessor processor) {
+ for (AssistStructure structure : mStructures) {
+ int nodes = structure.getWindowNodeCount();
+ for (int i = 0; i < nodes; i++) {
+ AssistStructure.ViewNode viewNode = structure.getWindowNodeAt(i).getRootViewNode();
+ traverseRoot(viewNode, processor);
+ }
+ }
+ }
+
+ private void traverseRoot(AssistStructure.ViewNode viewNode, NodeProcessor processor) {
+ processor.processNode(viewNode);
+ int childrenSize = viewNode.getChildCount();
+ if (childrenSize > 0) {
+ for (int i = 0; i < childrenSize; i++) {
+ traverseRoot(viewNode.getChildAt(i), processor);
+ }
+ }
+ }
+
+ public interface NodeProcessor {
+ void processNode(ViewNode node);
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/FakeFieldGenerator.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/FakeFieldGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d0754a94a6167c4a9181578546024b738179389
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/FakeFieldGenerator.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 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;
+
+import com.example.android.autofill.service.model.FilledAutofillField;
+
+interface FakeFieldGenerator {
+ FilledAutofillField generate(int seed, String datasetId);
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9acaa5a437d9cbda304e3c904b95045b36b83c9
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualActivity.java
@@ -0,0 +1,345 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.autofill.service.data.ClientViewMetadata;
+import com.example.android.autofill.service.data.ClientViewMetadataBuilder;
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.adapter.DatasetAdapter;
+import com.example.android.autofill.service.data.adapter.ResponseAdapter;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
+import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.settings.MyPreferences;
+import com.example.android.autofill.service.util.AppExecutors;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.GsonBuilder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE;
+import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT;
+import static com.example.android.autofill.service.util.Util.logd;
+
+/**
+ * When the user long-presses on an autofillable field and selects "Autofill", this activity is
+ * launched to allow the user to select the dataset.
+ */
+public class ManualActivity extends AppCompatActivity {
+
+ private static final int RC_SELECT_FIELD = 1;
+
+ // Unique id for dataset intents.
+ private static int sDatasetPendingIntentId = 0;
+
+ private LocalAutofillDataSource mLocalAutofillDataSource;
+ private DatasetAdapter mDatasetAdapter;
+ private ResponseAdapter mResponseAdapter;
+ private ClientViewMetadata mClientViewMetadata;
+ private String mPackageName;
+ private Intent mReplyIntent;
+ private MyPreferences mPreferences;
+ private List mAllDatasets;
+ private RecyclerView mRecyclerView;
+
+ public static IntentSender getManualIntentSenderForResponse(Context context) {
+ final Intent intent = new Intent(context, ManualActivity.class);
+ return PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.multidataset_service_manual_activity);
+ SharedPreferences sharedPreferences =
+ getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+ DefaultFieldTypesSource defaultFieldTypesSource =
+ DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
+ new GsonBuilder().create());
+ AutofillDao autofillDao = AutofillDatabase.getInstance(this,
+ defaultFieldTypesSource, new AppExecutors()).autofillDao();
+ mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(sharedPreferences,
+ autofillDao, new AppExecutors());
+ mPackageName = getPackageName();
+ mPreferences = MyPreferences.getInstance(this);
+ mRecyclerView = findViewById(R.id.suggestionsList);
+ mRecyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL));
+ mLocalAutofillDataSource.getAllAutofillDatasets(
+ new DataCallback>() {
+ @Override
+ public void onLoaded(List datasets) {
+ mAllDatasets = datasets;
+ buildAdapter();
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+
+ }
+ });
+ }
+
+ private void buildAdapter() {
+ List datasetIds = new ArrayList<>();
+ List datasetNames = new ArrayList<>();
+ List> allFieldTypes = new ArrayList<>();
+ for (DatasetWithFilledAutofillFields dataset : mAllDatasets) {
+ String datasetName = dataset.autofillDataset.getDatasetName();
+ String datasetId = dataset.autofillDataset.getId();
+ List fieldTypes = new ArrayList<>();
+ for (FilledAutofillField filledAutofillField : dataset.filledAutofillFields) {
+ fieldTypes.add(filledAutofillField.getFieldTypeName());
+ }
+ datasetIds.add(datasetId);
+ datasetNames.add(datasetName);
+ allFieldTypes.add(fieldTypes);
+ }
+ AutofillDatasetsAdapter adapter = new AutofillDatasetsAdapter(datasetIds, datasetNames,
+ allFieldTypes, this);
+ mRecyclerView.setAdapter(adapter);
+ }
+
+ @Override
+ public void finish() {
+ if (mReplyIntent != null) {
+ setResult(RESULT_OK, mReplyIntent);
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+ super.finish();
+ }
+
+ private void onFieldSelected(FilledAutofillField field, FieldType fieldType) {
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields = new DatasetWithFilledAutofillFields();
+ String newDatasetId = UUID.randomUUID().toString();
+ FilledAutofillField copyOfField = new FilledAutofillField(newDatasetId,
+ field.getFieldTypeName(), field.getTextValue(), field.getDateValue(),
+ field.getToggleValue());
+ String datasetName = "dataset-manual";
+ AutofillDataset autofillDataset = new AutofillDataset(newDatasetId, datasetName, mPackageName);
+ datasetWithFilledAutofillFields.filledAutofillFields = ImmutableList.of(copyOfField);
+ datasetWithFilledAutofillFields.autofillDataset = autofillDataset;
+ Intent intent = getIntent();
+ AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
+ ClientParser clientParser = new ClientParser(structure);
+ mReplyIntent = new Intent();
+ mLocalAutofillDataSource.getFieldTypeByAutofillHints(
+ new DataCallback>() {
+ @Override
+ public void onLoaded(HashMap fieldTypesByAutofillHint) {
+ ClientViewMetadataBuilder builder = new ClientViewMetadataBuilder(clientParser,
+ fieldTypesByAutofillHint);
+ mClientViewMetadata = builder.buildClientViewMetadata();
+ mDatasetAdapter = new DatasetAdapter(clientParser);
+ mResponseAdapter = new ResponseAdapter(ManualActivity.this,
+ mClientViewMetadata, mPackageName, mDatasetAdapter);
+ FillResponse fillResponse = mResponseAdapter.buildResponseForFocusedNode(
+ datasetName, field, fieldType);
+ setResponseIntent(fillResponse);
+ finish();
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode != RC_SELECT_FIELD || resultCode != RESULT_OK) {
+ logd("Ignoring requestCode == %d | resultCode == %d", requestCode,
+ resultCode);
+ return;
+ }
+ String datasetId = data.getStringExtra(ManualFieldPickerActivity.EXTRA_SELECTED_FIELD_DATASET_ID);
+ String fieldTypeName = data.getStringExtra(ManualFieldPickerActivity.EXTRA_SELECTED_FIELD_TYPE_NAME);
+ mLocalAutofillDataSource.getFilledAutofillField(datasetId, fieldTypeName, new DataCallback() {
+ @Override
+ public void onLoaded(FilledAutofillField field) {
+ mLocalAutofillDataSource.getFieldType(field.getFieldTypeName(), new DataCallback() {
+ @Override
+ public void onLoaded(FieldType fieldType) {
+ onFieldSelected(field, fieldType);
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+
+ }
+ });
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+
+ }
+ });
+ }
+
+
+ private void updateHeuristics() {
+// TODO: update heuristics in data source; something like:
+// mLocalAutofillDataSource.getAutofillDataset(mClientViewMetadata.getAllHints(),
+// datasetName, new DataCallback() {
+// @Override
+// public void onLoaded(DatasetWithFilledAutofillFields dataset) {
+// String datasetName = dataset.autofillDataset.getDatasetName();
+// RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+// mPackageName, datasetName);
+// setDatasetIntent(mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
+// dataset, remoteViews));
+// finish();
+// }
+//
+// @Override
+// public void onDataNotAvailable(String msg, Object... params) {
+// logw(msg, params);
+// finish();
+// }
+// });
+ }
+
+ private void setResponseIntent(FillResponse fillResponse) {
+ mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse);
+ }
+
+ private void setDatasetIntent(Dataset dataset) {
+ mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset);
+ }
+
+ /**
+ * Adapter for the {@link RecyclerView} that holds a list of datasets.
+ */
+ private static class AutofillDatasetsAdapter extends RecyclerView.Adapter {
+
+ private final List mDatasetIds;
+ private final List mDatasetNames;
+ private final List> mFieldTypes;
+ private final Activity mActivity;
+
+ AutofillDatasetsAdapter(List datasetIds, List datasetNames,
+ List> fieldTypes, Activity activity) {
+ mDatasetIds = datasetIds;
+ mDatasetNames = datasetNames;
+ mFieldTypes = fieldTypes;
+ mActivity = activity;
+ }
+
+ @Override
+ public DatasetViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return DatasetViewHolder.newInstance(parent, mActivity);
+ }
+
+ @Override
+ public void onBindViewHolder(final DatasetViewHolder holder, final int position) {
+ holder.bind(mDatasetIds.get(position), mDatasetNames.get(position),
+ mFieldTypes.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mDatasetNames.size();
+ }
+ }
+
+ /**
+ * Contains views needed in each row of the list of datasets.
+ */
+ private static class DatasetViewHolder extends RecyclerView.ViewHolder {
+ private final View mRootView;
+ private final TextView mDatasetNameText;
+ private final TextView mFieldTypesText;
+ private final Activity mActivity;
+
+ public DatasetViewHolder(View itemView, Activity activity) {
+ super(itemView);
+ mRootView = itemView;
+ mDatasetNameText = itemView.findViewById(R.id.datasetName);
+ mFieldTypesText = itemView.findViewById(R.id.fieldTypes);
+ mActivity = activity;
+ }
+
+ public static DatasetViewHolder newInstance(ViewGroup parent, Activity activity) {
+ return new DatasetViewHolder(LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.dataset_suggestion, parent, false), activity);
+ }
+
+ public void bind(String datasetId, String datasetName, List fieldTypes) {
+ mDatasetNameText.setText(datasetName);
+ String firstFieldType = null;
+ String secondFieldType = null;
+ int numOfFieldTypes = 0;
+ if (fieldTypes != null) {
+ numOfFieldTypes = fieldTypes.size();
+ if (numOfFieldTypes > 0) {
+ firstFieldType = fieldTypes.get(0);
+ }
+ if (numOfFieldTypes > 1) {
+ secondFieldType = fieldTypes.get(1);
+ }
+ }
+ String fieldTypesString;
+ if (numOfFieldTypes == 1) {
+ fieldTypesString = "Contains data for " + firstFieldType + ".";
+ } else if (numOfFieldTypes == 2) {
+ fieldTypesString = "Contains data for " + firstFieldType + " and " + secondFieldType + ".";
+ } else if (numOfFieldTypes > 2) {
+ fieldTypesString = "Contains data for " + firstFieldType + ", " + secondFieldType + ", and more.";
+ } else {
+ fieldTypesString = "Ignore: Contains no data.";
+ }
+ mFieldTypesText.setText(fieldTypesString);
+ mRootView.setOnClickListener((view) -> {
+ Intent intent = ManualFieldPickerActivity.getIntent(mActivity, datasetId);
+ mActivity.startActivityForResult(intent, RC_SELECT_FIELD);
+ });
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualFieldPickerActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualFieldPickerActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..608ac953b7d3b850dc89b22fca46c6b40295f7b7
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/ManualFieldPickerActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
+import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.util.AppExecutors;
+import com.google.gson.GsonBuilder;
+
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+public class ManualFieldPickerActivity extends AppCompatActivity {
+ private static final String EXTRA_DATASET_ID = "extra_dataset_id";
+ public static final String EXTRA_SELECTED_FIELD_DATASET_ID = "selected_field_dataset_id";
+ public static final String EXTRA_SELECTED_FIELD_TYPE_NAME = "selected_field_type_name";
+
+ private LocalAutofillDataSource mLocalAutofillDataSource;
+
+ private RecyclerView mRecyclerView;
+ private TextView mListTitle;
+ private DatasetWithFilledAutofillFields mDataset;
+
+ public static Intent getIntent(Context originContext, String datasetId) {
+ Intent intent = new Intent(originContext, ManualFieldPickerActivity.class);
+ intent.putExtra(EXTRA_DATASET_ID, datasetId);
+ return intent;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_field_picker);
+ SharedPreferences sharedPreferences = getSharedPreferences(
+ LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+ DefaultFieldTypesSource defaultFieldTypesSource =
+ DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
+ new GsonBuilder().create());
+ AutofillDao autofillDao = AutofillDatabase.getInstance(this,
+ defaultFieldTypesSource, new AppExecutors()).autofillDao();
+ String datasetId = getIntent().getStringExtra(EXTRA_DATASET_ID);
+ mRecyclerView = findViewById(R.id.fieldsList);
+ mRecyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL));
+ mListTitle = findViewById(R.id.listTitle);
+ mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(sharedPreferences,
+ autofillDao, new AppExecutors());
+ mLocalAutofillDataSource.getAutofillDatasetWithId(datasetId,
+ new DataCallback() {
+ @Override
+ public void onLoaded(DatasetWithFilledAutofillFields dataset) {
+ mDataset = dataset;
+ if (mDataset != null) {
+ onLoadedDataset();
+ }
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+
+ }
+ });
+ }
+
+ public void onSelectedDataset(FilledAutofillField field) {
+ Intent data = new Intent()
+ .putExtra(EXTRA_SELECTED_FIELD_DATASET_ID, field.getDatasetId())
+ .putExtra(EXTRA_SELECTED_FIELD_TYPE_NAME, field.getFieldTypeName());
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ public void onLoadedDataset() {
+ FieldsAdapter fieldsAdapter = new FieldsAdapter(this, mDataset.filledAutofillFields);
+ mRecyclerView.setAdapter(fieldsAdapter);
+ mListTitle.setText(getString(R.string.manual_data_picker_title,
+ mDataset.autofillDataset.getDatasetName()));
+ }
+
+ private static class FieldsAdapter extends RecyclerView.Adapter {
+ private final Activity mActivity;
+ private final List mFields;
+
+ public FieldsAdapter(Activity activity, List fields) {
+ mActivity = activity;
+ mFields = fields;
+ }
+
+ @Override
+ public FieldViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new FieldViewHolder(LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.dataset_field, parent, false), mActivity);
+ }
+
+ @Override
+ public void onBindViewHolder(FieldViewHolder holder, int position) {
+ FilledAutofillField field = mFields.get(position);
+ holder.bind(field);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mFields.size();
+ }
+ }
+
+ private static class FieldViewHolder extends RecyclerView.ViewHolder {
+ private final View mRootView;
+ private final TextView mFieldTypeText;
+ private final Activity mActivity;
+
+ public FieldViewHolder(View itemView, Activity activity) {
+ super(itemView);
+ mRootView = itemView;
+ mFieldTypeText = itemView.findViewById(R.id.fieldType);
+ mActivity = activity;
+ }
+
+ public void bind(FilledAutofillField field) {
+ mFieldTypeText.setText(field.getFieldTypeName());
+ mRootView.setOnClickListener((view) -> {
+ ((ManualFieldPickerActivity) mActivity).onSelectedDataset(field);
+ });
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9cdd6caaec2ac300eaaa042fb3dec2b0810b0f1
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.assist.AssistStructure;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.support.annotation.NonNull;
+import android.view.autofill.AutofillManager;
+import android.widget.RemoteViews;
+
+import com.example.android.autofill.service.data.AutofillDataBuilder;
+import com.example.android.autofill.service.data.ClientAutofillDataBuilder;
+import com.example.android.autofill.service.data.ClientViewMetadata;
+import com.example.android.autofill.service.data.ClientViewMetadataBuilder;
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.adapter.DatasetAdapter;
+import com.example.android.autofill.service.data.adapter.ResponseAdapter;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.PackageVerificationDataSource;
+import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
+import com.example.android.autofill.service.data.source.local.DigitalAssetLinksRepository;
+import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
+import com.example.android.autofill.service.data.source.local.SharedPrefsPackageVerificationRepository;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.DalCheck;
+import com.example.android.autofill.service.model.DalInfo;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.settings.MyPreferences;
+import com.example.android.autofill.service.util.AppExecutors;
+import com.example.android.autofill.service.util.Util;
+import com.google.gson.GsonBuilder;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement;
+import static com.example.android.autofill.service.util.Util.bundleToString;
+import static com.example.android.autofill.service.util.Util.dumpStructure;
+import static com.example.android.autofill.service.util.Util.logVerboseEnabled;
+import static com.example.android.autofill.service.util.Util.logd;
+import static com.example.android.autofill.service.util.Util.loge;
+import static com.example.android.autofill.service.util.Util.logv;
+import static com.example.android.autofill.service.util.Util.logw;
+import static java.util.stream.Collectors.toList;
+
+public class MyAutofillService extends AutofillService {
+
+ private LocalAutofillDataSource mLocalAutofillDataSource;
+ private DigitalAssetLinksRepository mDalRepository;
+ private PackageVerificationDataSource mPackageVerificationRepository;
+ private AutofillDataBuilder mAutofillDataBuilder;
+ private ResponseAdapter mResponseAdapter;
+ private ClientViewMetadata mClientViewMetadata;
+ private MyPreferences mPreferences;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mPreferences = MyPreferences.getInstance(this);
+ Util.setLoggingLevel(mPreferences.getLoggingLevel());
+ SharedPreferences localAfDataSourceSharedPrefs =
+ getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+ DefaultFieldTypesSource defaultFieldTypesSource =
+ DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
+ new GsonBuilder().create());
+ AutofillDao autofillDao = AutofillDatabase.getInstance(this,
+ defaultFieldTypesSource, new AppExecutors()).autofillDao();
+ mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(localAfDataSourceSharedPrefs,
+ autofillDao, new AppExecutors());
+ mDalRepository = DigitalAssetLinksRepository.getInstance(getPackageManager());
+ mPackageVerificationRepository = SharedPrefsPackageVerificationRepository.getInstance(this);
+ }
+
+ @Override
+ public void onFillRequest(@NonNull FillRequest request,
+ @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) {
+ List fillContexts = request.getFillContexts();
+ List structures =
+ fillContexts.stream().map(FillContext::getStructure).collect(toList());
+ AssistStructure latestStructure = fillContexts.get(fillContexts.size() - 1).getStructure();
+ ClientParser parser = new ClientParser(structures);
+
+ // Check user's settings for authenticating Responses and Datasets.
+ boolean responseAuth = mPreferences.isResponseAuth();
+ boolean datasetAuth = mPreferences.isDatasetAuth();
+ boolean manual = (request.getFlags() & FillRequest.FLAG_MANUAL_REQUEST) != 0;
+ mLocalAutofillDataSource.getFieldTypeByAutofillHints(
+ new DataCallback>() {
+ @Override
+ public void onLoaded(HashMap fieldTypesByAutofillHint) {
+ DatasetAdapter datasetAdapter = new DatasetAdapter(parser);
+ ClientViewMetadataBuilder clientViewMetadataBuilder =
+ new ClientViewMetadataBuilder(parser, fieldTypesByAutofillHint);
+ mClientViewMetadata = clientViewMetadataBuilder.buildClientViewMetadata();
+ mResponseAdapter = new ResponseAdapter(MyAutofillService.this,
+ mClientViewMetadata, getPackageName(), datasetAdapter);
+ String packageName = latestStructure.getActivityComponent().getPackageName();
+ if (!mPackageVerificationRepository.putPackageSignatures(packageName)) {
+ callback.onFailure(getString(R.string.invalid_package_signature));
+ return;
+ }
+ if (logVerboseEnabled()) {
+ logv("onFillRequest(): clientState=%s",
+ bundleToString(request.getClientState()));
+ dumpStructure(latestStructure);
+ }
+ cancellationSignal.setOnCancelListener(() ->
+ logw("Cancel autofill not implemented in this sample.")
+ );
+ fetchDataAndGenerateResponse(fieldTypesByAutofillHint, responseAuth,
+ datasetAuth, manual, callback);
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+
+ }
+ });
+ }
+
+ private void fetchDataAndGenerateResponse(
+ HashMap fieldTypesByAutofillHint, boolean responseAuth,
+ boolean datasetAuth, boolean manual, FillCallback callback) {
+ if (responseAuth) {
+ // If the entire Autofill Response is authenticated, AuthActivity is used
+ // to generate Response.
+ IntentSender sender = AuthActivity.getAuthIntentSenderForResponse(this);
+ RemoteViews remoteViews = RemoteViewsHelper.viewsWithAuth(getPackageName(),
+ getString(R.string.autofill_sign_in_prompt));
+ FillResponse response = mResponseAdapter.buildResponse(sender, remoteViews);
+ if (response != null) {
+ callback.onSuccess(response);
+ }
+ } else {
+ mLocalAutofillDataSource.getAutofillDatasets(mClientViewMetadata.getAllHints(),
+ new DataCallback>() {
+ @Override
+ public void onLoaded(List datasets) {
+ if ((datasets == null || datasets.isEmpty()) && manual) {
+ IntentSender sender = ManualActivity
+ .getManualIntentSenderForResponse(MyAutofillService.this);
+ RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+ getPackageName(),
+ getString(R.string.autofill_manual_prompt));
+ FillResponse response = mResponseAdapter.buildManualResponse(sender,
+ remoteViews);
+ if (response != null) {
+ callback.onSuccess(response);
+ }
+ } else {
+ FillResponse response = mResponseAdapter.buildResponse(
+ fieldTypesByAutofillHint, datasets, datasetAuth);
+ callback.onSuccess(response);
+ }
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+ logw(msg, params);
+ callback.onFailure(String.format(msg, params));
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) {
+ List fillContexts = request.getFillContexts();
+ List structures =
+ fillContexts.stream().map(FillContext::getStructure).collect(toList());
+ AssistStructure latestStructure = fillContexts.get(fillContexts.size() - 1).getStructure();
+ ClientParser parser = new ClientParser(structures);
+ mLocalAutofillDataSource.getFieldTypeByAutofillHints(
+ new DataCallback>() {
+ @Override
+ public void onLoaded(
+ HashMap fieldTypesByAutofillHint) {
+ mAutofillDataBuilder = new ClientAutofillDataBuilder(
+ fieldTypesByAutofillHint, getPackageName(), parser);
+ ClientViewMetadataBuilder clientViewMetadataBuilder =
+ new ClientViewMetadataBuilder(parser, fieldTypesByAutofillHint);
+ mClientViewMetadata = clientViewMetadataBuilder.buildClientViewMetadata();
+ String packageName = latestStructure.getActivityComponent().getPackageName();
+ if (!mPackageVerificationRepository.putPackageSignatures(packageName)) {
+ callback.onFailure(getString(R.string.invalid_package_signature));
+ return;
+ }
+ if (logVerboseEnabled()) {
+ logv("onSaveRequest(): clientState=%s",
+ bundleToString(request.getClientState()));
+ }
+ dumpStructure(latestStructure);
+ checkWebDomainAndBuildAutofillData(packageName, callback);
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+ loge("Should not happen - could not find field types.");
+ }
+ });
+ }
+
+ private void checkWebDomainAndBuildAutofillData(String packageName, SaveCallback callback) {
+ String webDomain;
+ try {
+ webDomain = mClientViewMetadata.getWebDomain();
+ } catch (SecurityException e) {
+ logw(e.getMessage());
+ callback.onFailure(getString(R.string.security_exception));
+ return;
+ }
+ if (webDomain != null && webDomain.length() > 0) {
+ DalCheckRequirement req = mPreferences.getDalCheckRequirement();
+ mDalRepository.checkValid(req, new DalInfo(webDomain, packageName),
+ new DataCallback() {
+ @Override
+ public void onLoaded(DalCheck dalCheck) {
+ if (dalCheck.linked) {
+ logd("Domain %s is valid for %s", webDomain, packageName);
+ buildAndSaveAutofillData();
+ } else {
+ loge("Could not associate web domain %s with app %s",
+ webDomain, packageName);
+ callback.onFailure(getString(R.string.dal_exception));
+ }
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+ logw(msg, params);
+ callback.onFailure(getString(R.string.dal_exception));
+ }
+ });
+ } else {
+ logd("no web domain");
+ buildAndSaveAutofillData();
+ }
+ }
+
+ private void buildAndSaveAutofillData() {
+ int datasetNumber = mLocalAutofillDataSource.getDatasetNumber();
+ List datasetsWithFilledAutofillFields =
+ mAutofillDataBuilder.buildDatasetsByPartition(datasetNumber);
+ mLocalAutofillDataSource.saveAutofillDatasets(datasetsWithFilledAutofillFields);
+ }
+
+ @Override
+ public void onConnected() {
+ logd("onConnected");
+ }
+
+ @Override
+ public void onDisconnected() {
+ logd("onDisconnected");
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b925b95ac55470e3c6035beba40ee83031bdb931
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.support.annotation.DrawableRes;
+import android.widget.RemoteViews;
+
+/**
+ * This is a class containing helper methods for building Autofill Datasets and Responses.
+ */
+public final class RemoteViewsHelper {
+ private RemoteViewsHelper() {
+ }
+
+ public static RemoteViews viewsWithAuth(String packageName, String text) {
+ return simpleRemoteViews(packageName, text, R.drawable.ic_lock_black_24dp);
+ }
+
+ public static RemoteViews viewsWithNoAuth(String packageName, String text) {
+ return simpleRemoteViews(packageName, text, R.drawable.ic_person_black_24dp);
+ }
+
+ private static RemoteViews simpleRemoteViews(String packageName, String remoteViewsText,
+ @DrawableRes int drawableId) {
+ RemoteViews presentation = new RemoteViews(packageName,
+ R.layout.multidataset_service_list_item);
+ presentation.setTextViewText(R.id.text, remoteViewsText);
+ presentation.setImageViewResource(R.id.icon, drawableId);
+ return presentation;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/W3cHints.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/W3cHints.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd8e9457641ae5173b1bf7d4afe4882b4d5f4981
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/W3cHints.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 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;
+
+public final class W3cHints {
+ // Optional W3C prefixes
+ public static final String PREFIX_SECTION = "section-";
+ public static final String SHIPPING = "shipping";
+ public static final String BILLING = "billing";
+ // W3C prefixes below...
+ public static final String PREFIX_HOME = "home";
+ public static final String PREFIX_WORK = "work";
+ public static final String PREFIX_FAX = "fax";
+ public static final String PREFIX_PAGER = "pager";
+ // ... require those suffix
+ public static final String TEL = "tel";
+ public static final String TEL_COUNTRY_CODE = "tel-country-code";
+ public static final String TEL_NATIONAL = "tel-national";
+ public static final String TEL_AREA_CODE = "tel-area-code";
+ public static final String TEL_LOCAL = "tel-local";
+ public static final String TEL_LOCAL_PREFIX = "tel-local-prefix";
+ public static final String TEL_LOCAL_SUFFIX = "tel-local-suffix";
+ public static final String TEL_EXTENSION = "tel_extension";
+ public static final String EMAIL = "email";
+ public static final String IMPP = "impp";
+
+ private W3cHints() {
+ }
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..0110bdd2a3511f93864238d919cb5a07f432e0c7
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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.data;
+
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+
+import java.util.List;
+
+public interface AutofillDataBuilder {
+ List buildDatasetsByPartition(int datasetNumber);
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d268337f0e3ae643416d6c36ff80e8591f49a20
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 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.data;
+
+import android.app.assist.AssistStructure;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.view.autofill.AutofillValue;
+
+import com.example.android.autofill.service.AutofillHints;
+import com.example.android.autofill.service.ClientParser;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.google.common.collect.ImmutableList;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import static com.example.android.autofill.service.util.Util.loge;
+
+public class ClientAutofillDataBuilder implements AutofillDataBuilder {
+ private final ClientParser mClientParser;
+ private final HashMap mFieldTypesByAutofillHint;
+ private final String mPackageName;
+
+ public ClientAutofillDataBuilder(HashMap fieldTypesByAutofillHint,
+ String packageName, ClientParser clientParser) {
+ mClientParser = clientParser;
+ mFieldTypesByAutofillHint = fieldTypesByAutofillHint;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public List buildDatasetsByPartition(int datasetNumber) {
+ ImmutableList.Builder listBuilder =
+ new ImmutableList.Builder<>();
+ for (int partition : AutofillHints.PARTITIONS) {
+ AutofillDataset autofillDataset = new AutofillDataset(UUID.randomUUID().toString(),
+ "dataset-" + datasetNumber + "." + partition, mPackageName);
+ DatasetWithFilledAutofillFields dataset =
+ buildDatasetForPartition(autofillDataset, partition);
+ if (dataset != null && dataset.filledAutofillFields != null) {
+ listBuilder.add(dataset);
+ }
+ }
+ return listBuilder.build();
+ }
+
+ /**
+ * Parses a client view structure and build a dataset (in the form of a
+ * {@link DatasetWithFilledAutofillFields}) from the view metadata found.
+ */
+ private DatasetWithFilledAutofillFields buildDatasetForPartition(AutofillDataset dataset,
+ int partition) {
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
+ new DatasetWithFilledAutofillFields();
+ datasetWithFilledAutofillFields.autofillDataset = dataset;
+ mClientParser.parse((node) ->
+ parseAutofillFields(node, datasetWithFilledAutofillFields, partition)
+ );
+ return datasetWithFilledAutofillFields;
+
+ }
+
+ private void parseAutofillFields(AssistStructure.ViewNode viewNode,
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, int partition) {
+ String[] hints = viewNode.getAutofillHints();
+ if (hints == null || hints.length == 0) {
+ return;
+ }
+ AutofillValue autofillValue = viewNode.getAutofillValue();
+ String textValue = null;
+ Long dateValue = null;
+ Boolean toggleValue = null;
+ CharSequence[] autofillOptions = null;
+ Integer listIndex = null;
+ if (autofillValue != null) {
+ if (autofillValue.isText()) {
+ // Using toString of AutofillValue.getTextValue in order to save it to
+ // SharedPreferences.
+ textValue = autofillValue.getTextValue().toString();
+ } else if (autofillValue.isDate()) {
+ dateValue = autofillValue.getDateValue();
+ } else if (autofillValue.isList()) {
+ autofillOptions = viewNode.getAutofillOptions();
+ listIndex = autofillValue.getListValue();
+ } else if (autofillValue.isToggle()) {
+ toggleValue = autofillValue.getToggleValue();
+ }
+ }
+ appendViewMetadata(datasetWithFilledAutofillFields,
+ hints, partition, textValue, dateValue, toggleValue,
+ autofillOptions, listIndex);
+ }
+
+ private void appendViewMetadata(@NonNull DatasetWithFilledAutofillFields
+ datasetWithFilledAutofillFields, @NonNull String[] hints, int partition,
+ @Nullable String textValue, @Nullable Long dateValue, @Nullable Boolean toggleValue,
+ @Nullable CharSequence[] autofillOptions, @Nullable Integer listIndex) {
+ for (int i = 0; i < hints.length; i++) {
+ String hint = hints[i];
+ // Then check if the "actual" hint is supported.
+ FieldTypeWithHeuristics fieldTypeWithHeuristics = mFieldTypesByAutofillHint.get(hint);
+ if (fieldTypeWithHeuristics != null) {
+ FieldType fieldType = fieldTypeWithHeuristics.fieldType;
+ if (!AutofillHints.matchesPartition(fieldType.getPartition(), partition)) {
+ continue;
+ }
+ // Only add the field if the hint is supported by the type.
+ if (textValue != null) {
+ if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_TEXT)) {
+ loge("Text is invalid type for hint '%s'", hint);
+ }
+ }
+ if (autofillOptions != null && listIndex != null &&
+ autofillOptions.length > listIndex) {
+ if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_LIST)) {
+ loge("List is invalid type for hint '%s'", hint);
+ }
+ textValue = autofillOptions[listIndex].toString();
+ }
+ if (dateValue != null) {
+ if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_DATE)) {
+ loge("Date is invalid type for hint '%s'", hint);
+ }
+ }
+ if (toggleValue != null) {
+ if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_TOGGLE)) {
+ loge("Toggle is invalid type for hint '%s'", hint);
+ }
+ }
+ String datasetId = datasetWithFilledAutofillFields.autofillDataset.getId();
+ datasetWithFilledAutofillFields.add(new FilledAutofillField(datasetId,
+ fieldType.getTypeName(), textValue, dateValue, toggleValue));
+ } else {
+ loge("Invalid hint: %s", hint);
+ }
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java
new file mode 100644
index 0000000000000000000000000000000000000000..85b789ef7f39a3400bfaa11ab5d4def87ddc6b0c
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.data;
+
+import android.service.autofill.SaveInfo;
+import android.view.autofill.AutofillId;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * In this simple implementation, the only view data we collect from the client are autofill hints
+ * of the views in the view hierarchy, the corresponding autofill IDs, and the {@link SaveInfo}
+ * based on the hints.
+ */
+public class ClientViewMetadata {
+ private final List mAllHints;
+ private final int mSaveType;
+ private final AutofillId[] mAutofillIds;
+ private final String mWebDomain;
+ private final AutofillId[] mFocusedIds;
+
+ public ClientViewMetadata(List allHints, int saveType, AutofillId[] autofillIds,
+ AutofillId[] focusedIds, String webDomain) {
+ mAllHints = allHints;
+ mSaveType = saveType;
+ mAutofillIds = autofillIds;
+ mWebDomain = webDomain;
+ mFocusedIds = focusedIds;
+ }
+
+ public List getAllHints() {
+ return mAllHints;
+ }
+
+ public AutofillId[] getAutofillIds() {
+ return mAutofillIds;
+ }
+
+ public AutofillId[] getFocusedIds() {
+ return mFocusedIds;
+ }
+
+ public int getSaveType() {
+ return mSaveType;
+ }
+
+ public String getWebDomain() {
+ return mWebDomain;
+ }
+
+ @Override public String toString() {
+ return "ClientViewMetadata{" +
+ "mAllHints=" + mAllHints +
+ ", mSaveType=" + mSaveType +
+ ", mAutofillIds=" + Arrays.toString(mAutofillIds) +
+ ", mWebDomain='" + mWebDomain + '\'' +
+ ", mFocusedIds=" + Arrays.toString(mFocusedIds) +
+ '}';
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadataBuilder.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadataBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..40a67efb8bb7ac1ce04fa3ceecb873767307643a
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadataBuilder.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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.data;
+
+
+import android.app.assist.AssistStructure;
+import android.util.MutableInt;
+import android.view.autofill.AutofillId;
+
+import com.example.android.autofill.service.ClientParser;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import static com.example.android.autofill.service.util.Util.logd;
+
+public class ClientViewMetadataBuilder {
+ private ClientParser mClientParser;
+ private HashMap mFieldTypesByAutofillHint;
+
+ public ClientViewMetadataBuilder(ClientParser parser,
+ HashMap fieldTypesByAutofillHint) {
+ mClientParser = parser;
+ mFieldTypesByAutofillHint = fieldTypesByAutofillHint;
+ }
+
+ public ClientViewMetadata buildClientViewMetadata() {
+ List allHints = new ArrayList<>();
+ MutableInt saveType = new MutableInt(0);
+ List autofillIds = new ArrayList<>();
+ StringBuilder webDomainBuilder = new StringBuilder();
+ List focusedAutofillIds = new ArrayList<>();
+ mClientParser.parse((node) -> parseNode(node, allHints, saveType, autofillIds, focusedAutofillIds));
+ mClientParser.parse((node) -> parseWebDomain(node, webDomainBuilder));
+ String webDomain = webDomainBuilder.toString();
+ AutofillId[] autofillIdsArray = autofillIds.toArray(new AutofillId[autofillIds.size()]);
+ AutofillId[] focusedIds = focusedAutofillIds.toArray(new AutofillId[focusedAutofillIds.size()]);
+ return new ClientViewMetadata(allHints, saveType.value, autofillIdsArray, focusedIds, webDomain);
+ }
+
+ private void parseWebDomain(AssistStructure.ViewNode viewNode, StringBuilder validWebDomain) {
+ String webDomain = viewNode.getWebDomain();
+ if (webDomain != null) {
+ logd("child web domain: %s", webDomain);
+ if (validWebDomain.length() > 0) {
+ if (!webDomain.equals(validWebDomain.toString())) {
+ throw new SecurityException("Found multiple web domains: valid= "
+ + validWebDomain + ", child=" + webDomain);
+ }
+ } else {
+ validWebDomain.append(webDomain);
+ }
+ }
+ }
+
+ private void parseNode(AssistStructure.ViewNode root, List allHints,
+ MutableInt autofillSaveType, List autofillIds,
+ List focusedAutofillIds) {
+ String[] hints = root.getAutofillHints();
+ if (hints != null) {
+ for (String hint : hints) {
+ FieldTypeWithHeuristics fieldTypeWithHints = mFieldTypesByAutofillHint.get(hint);
+ if (fieldTypeWithHints != null && fieldTypeWithHints.fieldType != null) {
+ allHints.add(hint);
+ autofillSaveType.value |= fieldTypeWithHints.fieldType.getSaveInfo();
+ autofillIds.add(root.getAutofillId());
+ }
+ }
+ }
+ if (root.isFocused()) {
+ focusedAutofillIds.add(root.getAutofillId());
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/DataCallback.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/DataCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..e76560284166c1dd1bc9a56fccd92a3e00c403ef
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/DataCallback.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 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.data;
+
+public interface DataCallback {
+ void onLoaded(T object);
+
+ void onDataNotAvailable(String msg, Object... params);
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..67ee88034e6c043a91a3bb9bb075a49c87378699
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.data;
+
+import com.example.android.autofill.service.AutofillHints;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.UUID;
+
+public class FakeAutofillDataBuilder implements AutofillDataBuilder {
+ private final List mFieldTypesWithHints;
+ private final String mPackageName;
+ private final int mSeed;
+
+ public FakeAutofillDataBuilder(List fieldTypesWithHints,
+ String packageName, int seed) {
+ mFieldTypesWithHints = fieldTypesWithHints;
+ mSeed = seed;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public List buildDatasetsByPartition(int datasetNumber) {
+ ImmutableList.Builder listBuilder =
+ new ImmutableList.Builder<>();
+ for (int partition : AutofillHints.PARTITIONS) {
+ AutofillDataset autofillDataset = new AutofillDataset(UUID.randomUUID().toString(),
+ "dataset-" + datasetNumber + "." + partition, mPackageName);
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
+ buildCollectionForPartition(autofillDataset, partition);
+ if (datasetWithFilledAutofillFields != null &&
+ datasetWithFilledAutofillFields.filledAutofillFields != null &&
+ !datasetWithFilledAutofillFields.filledAutofillFields.isEmpty()) {
+ listBuilder.add(datasetWithFilledAutofillFields);
+ }
+ }
+ return listBuilder.build();
+ }
+
+ private DatasetWithFilledAutofillFields buildCollectionForPartition(
+ AutofillDataset dataset, int partition) {
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
+ new DatasetWithFilledAutofillFields();
+ datasetWithFilledAutofillFields.autofillDataset = dataset;
+ for (FieldTypeWithHeuristics fieldTypeWithHeuristics : mFieldTypesWithHints) {
+ if (AutofillHints.matchesPartition(
+ fieldTypeWithHeuristics.getFieldType().getPartition(), partition)) {
+ FilledAutofillField fakeField =
+ AutofillHints.generateFakeField(fieldTypeWithHeuristics, mPackageName,
+ mSeed, dataset.getId());
+ datasetWithFilledAutofillFields.add(fakeField);
+ }
+ }
+ return datasetWithFilledAutofillFields;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..6f7e60368f401f9f6cdb4e482b8060e663f61c51
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 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.data.adapter;
+
+import android.app.assist.AssistStructure;
+import android.content.IntentSender;
+import android.service.autofill.Dataset;
+import android.util.MutableBoolean;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import com.example.android.autofill.service.AutofillHints;
+import com.example.android.autofill.service.ClientParser;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import static com.example.android.autofill.service.util.Util.indexOf;
+import static com.example.android.autofill.service.util.Util.logv;
+import static com.example.android.autofill.service.util.Util.logw;
+import static java.util.stream.Collectors.toMap;
+
+public class DatasetAdapter {
+ private final ClientParser mClientParser;
+
+ public DatasetAdapter(ClientParser clientParser) {
+ mClientParser = clientParser;
+ }
+
+ /**
+ * Wraps autofill data in a {@link Dataset} object which can then be sent back to the client.
+ */
+ public Dataset buildDataset(HashMap fieldTypesByAutofillHint,
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
+ RemoteViews remoteViews) {
+ return buildDataset(fieldTypesByAutofillHint, datasetWithFilledAutofillFields, remoteViews,
+ null);
+ }
+
+ public Dataset buildDatasetForFocusedNode(FilledAutofillField filledAutofillField,
+ FieldType fieldType, RemoteViews remoteViews) {
+ Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews);
+ boolean setAtLeastOneValue = bindDatasetToFocusedNode(filledAutofillField,
+ fieldType, datasetBuilder);
+ if (!setAtLeastOneValue) {
+ return null;
+ }
+ return datasetBuilder.build();
+ }
+
+ /**
+ * Wraps autofill data in a {@link Dataset} object with an IntentSender, which can then be
+ * sent back to the client.
+ */
+ public Dataset buildDataset(HashMap fieldTypesByAutofillHint,
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
+ RemoteViews remoteViews, IntentSender intentSender) {
+ Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews);
+ if (intentSender != null) {
+ datasetBuilder.setAuthentication(intentSender);
+ }
+ boolean setAtLeastOneValue = bindDataset(fieldTypesByAutofillHint,
+ datasetWithFilledAutofillFields, datasetBuilder);
+ if (!setAtLeastOneValue) {
+ return null;
+ }
+ return datasetBuilder.build();
+ }
+
+ /**
+ * Build an autofill {@link Dataset} using saved data and the client's AssistStructure.
+ */
+ private boolean bindDataset(HashMap fieldTypesByAutofillHint,
+ DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
+ Dataset.Builder datasetBuilder) {
+ MutableBoolean setValueAtLeastOnce = new MutableBoolean(false);
+ Map filledAutofillFieldsByTypeName =
+ datasetWithFilledAutofillFields.filledAutofillFields.stream()
+ .collect(toMap(FilledAutofillField::getFieldTypeName, Function.identity()));
+ mClientParser.parse((node) ->
+ parseAutofillFields(node, fieldTypesByAutofillHint, filledAutofillFieldsByTypeName,
+ datasetBuilder, setValueAtLeastOnce)
+ );
+ return setValueAtLeastOnce.value;
+ }
+
+ private boolean bindDatasetToFocusedNode(FilledAutofillField field,
+ FieldType fieldType, Dataset.Builder builder) {
+ MutableBoolean setValueAtLeastOnce = new MutableBoolean(false);
+ mClientParser.parse((node) -> {
+ if (node.isFocused() && node.getAutofillId() != null) {
+ bindValueToNode(node, field, builder, setValueAtLeastOnce);
+ }
+ });
+ return setValueAtLeastOnce.value;
+ }
+
+ private void parseAutofillFields(AssistStructure.ViewNode viewNode,
+ HashMap fieldTypesByAutofillHint,
+ Map filledAutofillFieldsByTypeName,
+ Dataset.Builder builder, MutableBoolean setValueAtLeastOnce) {
+ String[] rawHints = viewNode.getAutofillHints();
+ if (rawHints == null || rawHints.length == 0) {
+ logv("No af hints at ViewNode - %s", viewNode.getIdEntry());
+ return;
+ }
+ String fieldTypeName = AutofillHints.getFieldTypeNameFromAutofillHints(
+ fieldTypesByAutofillHint, Arrays.asList(rawHints));
+ if (fieldTypeName == null) {
+ return;
+ }
+ FilledAutofillField field = filledAutofillFieldsByTypeName.get(fieldTypeName);
+ if (field == null) {
+ return;
+ }
+ bindValueToNode(viewNode, field, builder, setValueAtLeastOnce);
+ }
+
+ void bindValueToNode(AssistStructure.ViewNode viewNode,
+ FilledAutofillField field, Dataset.Builder builder,
+ MutableBoolean setValueAtLeastOnce) {
+ AutofillId autofillId = viewNode.getAutofillId();
+ if (autofillId == null) {
+ logw("Autofill ID null for %s", viewNode.toString());
+ return;
+ }
+ int autofillType = viewNode.getAutofillType();
+ switch (autofillType) {
+ case View.AUTOFILL_TYPE_LIST:
+ CharSequence[] options = viewNode.getAutofillOptions();
+ int listValue = -1;
+ if (options != null) {
+ listValue = indexOf(viewNode.getAutofillOptions(), field.getTextValue());
+ }
+ if (listValue != -1) {
+ builder.setValue(autofillId, AutofillValue.forList(listValue));
+ setValueAtLeastOnce.value = true;
+ }
+ break;
+ case View.AUTOFILL_TYPE_DATE:
+ Long dateValue = field.getDateValue();
+ if (dateValue != null) {
+ builder.setValue(autofillId, AutofillValue.forDate(dateValue));
+ setValueAtLeastOnce.value = true;
+ }
+ break;
+ case View.AUTOFILL_TYPE_TEXT:
+ String textValue = field.getTextValue();
+ if (textValue != null) {
+ builder.setValue(autofillId, AutofillValue.forText(textValue));
+ setValueAtLeastOnce.value = true;
+ }
+ break;
+ case View.AUTOFILL_TYPE_TOGGLE:
+ Boolean toggleValue = field.getToggleValue();
+ if (toggleValue != null) {
+ builder.setValue(autofillId, AutofillValue.forToggle(toggleValue));
+ setValueAtLeastOnce.value = true;
+ }
+ break;
+ case View.AUTOFILL_TYPE_NONE:
+ default:
+ logw("Invalid autofill type - %d", autofillType);
+ break;
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..0300fed641defa554282ccf80cf2222438a7c275
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 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.data.adapter;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import com.example.android.autofill.service.AuthActivity;
+import com.example.android.autofill.service.RemoteViewsHelper;
+import com.example.android.autofill.service.data.ClientViewMetadata;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class ResponseAdapter {
+ private final Context mContext;
+ private final DatasetAdapter mDatasetAdapter;
+ private final String mPackageName;
+ private final ClientViewMetadata mClientViewMetadata;
+
+ public ResponseAdapter(Context context, ClientViewMetadata clientViewMetadata,
+ String packageName, DatasetAdapter datasetAdapter) {
+ mContext = context;
+ mClientViewMetadata = clientViewMetadata;
+ mDatasetAdapter = datasetAdapter;
+ mPackageName = packageName;
+ }
+
+ public FillResponse buildResponseForFocusedNode(String datasetName, FilledAutofillField field,
+ FieldType fieldType) {
+ FillResponse.Builder responseBuilder = new FillResponse.Builder();
+ RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+ mPackageName, datasetName);
+ Dataset dataset = mDatasetAdapter.buildDatasetForFocusedNode(field, fieldType, remoteViews);
+ if (dataset != null) {
+ responseBuilder.addDataset(dataset);
+ return responseBuilder.build();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Wraps autofill data in a Response object (essentially a series of Datasets) which can then
+ * be sent back to the client View.
+ */
+ public FillResponse buildResponse(HashMap fieldTypesByAutofillHint,
+ List datasets, boolean datasetAuth) {
+ FillResponse.Builder responseBuilder = new FillResponse.Builder();
+ if (datasets != null) {
+ for (DatasetWithFilledAutofillFields datasetWithFilledAutofillFields : datasets) {
+ if (datasetWithFilledAutofillFields != null) {
+ Dataset dataset;
+ String datasetName = datasetWithFilledAutofillFields.autofillDataset
+ .getDatasetName();
+ if (datasetAuth) {
+ IntentSender intentSender = AuthActivity.getAuthIntentSenderForDataset(
+ mContext, datasetName);
+ RemoteViews remoteViews = RemoteViewsHelper.viewsWithAuth(
+ mPackageName, datasetName);
+ dataset = mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
+ datasetWithFilledAutofillFields, remoteViews, intentSender);
+ } else {
+ RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
+ mPackageName, datasetName);
+ dataset = mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
+ datasetWithFilledAutofillFields, remoteViews);
+ }
+ if (dataset != null) {
+ responseBuilder.addDataset(dataset);
+ }
+ }
+ }
+ }
+ int saveType = mClientViewMetadata.getSaveType();
+ AutofillId[] autofillIds = mClientViewMetadata.getAutofillIds();
+ if (autofillIds != null && autofillIds.length > 0) {
+ SaveInfo saveInfo = new SaveInfo.Builder(saveType, autofillIds).build();
+ responseBuilder.setSaveInfo(saveInfo);
+ return responseBuilder.build();
+ } else {
+ return null;
+ }
+ }
+
+ public FillResponse buildResponse(IntentSender sender, RemoteViews remoteViews) {
+ FillResponse.Builder responseBuilder = new FillResponse.Builder();
+ int saveType = mClientViewMetadata.getSaveType();
+ AutofillId[] autofillIds = mClientViewMetadata.getAutofillIds();
+ if (autofillIds != null && autofillIds.length > 0) {
+ SaveInfo saveInfo = new SaveInfo.Builder(saveType, autofillIds).build();
+ responseBuilder.setSaveInfo(saveInfo);
+ responseBuilder.setAuthentication(autofillIds, sender, remoteViews);
+ return responseBuilder.build();
+ } else {
+ return null;
+ }
+ }
+
+ public FillResponse buildManualResponse(IntentSender sender, RemoteViews remoteViews) {
+ FillResponse.Builder responseBuilder = new FillResponse.Builder();
+ int saveType = mClientViewMetadata.getSaveType();
+ AutofillId[] focusedIds = mClientViewMetadata.getFocusedIds();
+ if (focusedIds != null && focusedIds.length > 0) {
+ SaveInfo saveInfo = new SaveInfo.Builder(saveType, focusedIds).build();
+ return responseBuilder.setSaveInfo(saveInfo)
+ .setAuthentication(focusedIds, sender, remoteViews)
+ .build();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3579dd8561bb9e01fcd2cf65480d550941b9fe9
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.data.source;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.model.ResourceIdHeuristic;
+
+import java.util.HashMap;
+import java.util.List;
+
+public interface AutofillDataSource {
+
+ /**
+ * Asynchronously gets saved list of {@link DatasetWithFilledAutofillFields} that contains some
+ * objects that can autofill fields with these {@code autofillHints}.
+ */
+ void getAutofillDatasets(List allAutofillHints,
+ DataCallback> datasetsCallback);
+
+ void getAllAutofillDatasets(
+ DataCallback> datasetsCallback);
+
+ /**
+ * Asynchronously gets a saved {@link DatasetWithFilledAutofillFields} for a specific
+ * {@code datasetName} that contains some objects that can autofill fields with these
+ * {@code autofillHints}.
+ */
+ void getAutofillDataset(List allAutofillHints,
+ String datasetName, DataCallback datasetsCallback);
+
+ /**
+ * Stores a collection of Autofill fields.
+ */
+ void saveAutofillDatasets(List
+ datasetsWithFilledAutofillFields);
+
+ void saveResourceIdHeuristic(ResourceIdHeuristic resourceIdHeuristic);
+
+ /**
+ * Gets all autofill field types.
+ */
+ void getFieldTypes(DataCallback> fieldTypesCallback);
+
+ /**
+ * Gets all autofill field types.
+ */
+ void getFieldType(String typeName, DataCallback fieldTypeCallback);
+
+ void getFieldTypeByAutofillHints(
+ DataCallback> fieldTypeMapCallback);
+
+ void getFilledAutofillField(String datasetId, String fieldTypeName, DataCallback fieldCallback);
+
+ /**
+ * Clears all data.
+ */
+ void clear();
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DalService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DalService.java
new file mode 100644
index 0000000000000000000000000000000000000000..b9cf695680227eb7da43c016546e08a6d2402fa4
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DalService.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.data.source;
+
+import com.example.android.autofill.service.model.DalCheck;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+
+public interface DalService {
+ @GET("/v1/assetlinks:check")
+ Call check(@Query("source.web.site") String webDomain,
+ @Query("relation") String permission,
+ @Query("target.android_app.package_name") String packageName,
+ @Query("target.android_app.certificate.sha256_fingerprint") String fingerprint);
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DefaultFieldTypesSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DefaultFieldTypesSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..22cbeadce371456d09de6e40dc91ee24fffb9d41
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DefaultFieldTypesSource.java
@@ -0,0 +1,25 @@
+/*
+ * 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.data.source;
+
+import com.example.android.autofill.service.model.DefaultFieldTypeWithHints;
+
+import java.util.List;
+
+public interface DefaultFieldTypesSource {
+ List getDefaultFieldTypes();
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DigitalAssetLinksDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DigitalAssetLinksDataSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..00661a7f9d48af036bfe956a1b7bd2b1eb681e08
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DigitalAssetLinksDataSource.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.data.source;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.model.DalCheck;
+import com.example.android.autofill.service.model.DalInfo;
+
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement;
+
+/**
+ * Data source for
+ * Digital Asset Links .
+ */
+public interface DigitalAssetLinksDataSource {
+
+ /**
+ * Checks if the association between a web domain and a package is valid.
+ */
+ void checkValid(DalCheckRequirement dalCheckRequirement, DalInfo dalInfo,
+ DataCallback dalCheckCallback);
+
+ /**
+ * Clears all cached data.
+ */
+ void clear();
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/PackageVerificationDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/PackageVerificationDataSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e271e028b636c4c5a824a39d9df05b8a7ecfa41
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/PackageVerificationDataSource.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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.data.source;
+
+public interface PackageVerificationDataSource {
+
+ /**
+ * Verifies that the signatures in the passed {@code Context} match what is currently in
+ * storage. If there are no current signatures in storage for this packageName, it will store
+ * the signatures from the passed {@code Context}.
+ *
+ * @return {@code true} if signatures for this packageName are not currently in storage
+ * or if the signatures in the passed {@code Context} match what is currently in storage;
+ * {@code false} if the signatures in the passed {@code Context} do not match what is
+ * currently in storage or if an {@code Exception} was thrown while generating the signatures.
+ */
+ boolean putPackageSignatures(String packageName);
+
+ /**
+ * Clears all signature data currently in storage.
+ */
+ void clear();
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DefaultFieldTypesLocalJsonSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DefaultFieldTypesLocalJsonSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..d403f320bd66c9f1230586eba9e258bf8e8283b7
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DefaultFieldTypesLocalJsonSource.java
@@ -0,0 +1,68 @@
+/*
+ * 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.data.source.local;
+
+import android.content.res.Resources;
+
+import com.example.android.autofill.service.R;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.model.DefaultFieldTypeWithHints;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.reflect.Type;
+import java.util.List;
+
+import static com.example.android.autofill.service.util.Util.loge;
+
+public class DefaultFieldTypesLocalJsonSource implements DefaultFieldTypesSource {
+ private static DefaultFieldTypesLocalJsonSource sInstance;
+
+ private final Resources mResources;
+ private final Gson mGson;
+
+ private DefaultFieldTypesLocalJsonSource(Resources resources, Gson gson) {
+ mResources = resources;
+ mGson = gson;
+ }
+
+ public static DefaultFieldTypesLocalJsonSource getInstance(Resources resources, Gson gson) {
+ if (sInstance == null) {
+ sInstance = new DefaultFieldTypesLocalJsonSource(resources, gson);
+ }
+ return sInstance;
+ }
+
+ @Override
+ public List getDefaultFieldTypes() {
+ Type fieldTypeListType = TypeToken.getParameterized(List.class,
+ DefaultFieldTypeWithHints.class).getType();
+ InputStream is = mResources.openRawResource(R.raw.default_field_types);
+ List fieldTypes = null;
+ try(Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
+ fieldTypes = mGson.fromJson(reader, fieldTypeListType);
+ } catch (IOException e) {
+ loge(e, "Exception during deserialization of FieldTypes.");
+ }
+ return fieldTypes;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DigitalAssetLinksRepository.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DigitalAssetLinksRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..6303a1e728c352f0b332d65e0854c0118fe04dc2
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DigitalAssetLinksRepository.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017 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.data.source.local;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.support.annotation.NonNull;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.source.DalService;
+import com.example.android.autofill.service.data.source.DigitalAssetLinksDataSource;
+import com.example.android.autofill.service.model.DalCheck;
+import com.example.android.autofill.service.model.DalInfo;
+import com.example.android.autofill.service.util.SecurityHelper;
+import com.google.common.net.InternetDomainName;
+
+import java.util.HashMap;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement;
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.AllUrls;
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.Disabled;
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.LoginOnly;
+import static com.example.android.autofill.service.util.Util.logd;
+
+
+/**
+ * Singleton repository that caches the result of Digital Asset Links checks.
+ */
+public class DigitalAssetLinksRepository implements DigitalAssetLinksDataSource {
+ private static final String DAL_BASE_URL = "https://digitalassetlinks.googleapis.com";
+ private static final String PERMISSION_GET_LOGIN_CREDS = "common.get_login_creds";
+ private static final String PERMISSION_HANDLE_ALL_URLS = "common.handle_all_urls";
+ private static DigitalAssetLinksRepository sInstance;
+
+ private final PackageManager mPackageManager;
+ private final DalService mDalService;
+ private final HashMap mCache;
+
+ private DigitalAssetLinksRepository(PackageManager packageManager) {
+ mPackageManager = packageManager;
+ mCache = new HashMap<>();
+ mDalService = new Retrofit.Builder()
+ .baseUrl(DAL_BASE_URL)
+ .build()
+ .create(DalService.class);
+ }
+
+ public static DigitalAssetLinksRepository getInstance(PackageManager packageManager) {
+ if (sInstance == null) {
+ sInstance = new DigitalAssetLinksRepository(packageManager);
+ }
+ return sInstance;
+ }
+
+ public static String getCanonicalDomain(String domain) {
+ InternetDomainName idn = InternetDomainName.from(domain);
+ while (idn != null && !idn.isTopPrivateDomain()) {
+ idn = idn.parent();
+ }
+ return idn == null ? null : idn.toString();
+ }
+
+ @Override
+ public void clear() {
+ mCache.clear();
+ }
+
+ public void checkValid(DalCheckRequirement dalCheckRequirement, DalInfo dalInfo,
+ DataCallback dalCheckDataCallback) {
+ if (dalCheckRequirement.equals(Disabled)) {
+ DalCheck dalCheck = new DalCheck();
+ dalCheck.linked = true;
+ dalCheckDataCallback.onLoaded(dalCheck);
+ return;
+ }
+
+ DalCheck dalCheck = mCache.get(dalInfo);
+ if (dalCheck != null) {
+ dalCheckDataCallback.onLoaded(dalCheck);
+ return;
+ }
+ String packageName = dalInfo.getPackageName();
+ String webDomain = dalInfo.getWebDomain();
+
+ final String fingerprint;
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
+ fingerprint = SecurityHelper.getFingerprint(packageInfo, packageName);
+ } catch (Exception e) {
+ dalCheckDataCallback.onDataNotAvailable("Error getting fingerprint for %s",
+ packageName);
+ return;
+ }
+ logd("validating domain %s for pkg %s and fingerprint %s.", webDomain,
+ packageName, fingerprint);
+ mDalService.check(webDomain, PERMISSION_GET_LOGIN_CREDS, packageName, fingerprint).enqueue(
+ new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call,
+ @NonNull Response response) {
+ DalCheck dalCheck = response.body();
+ if (dalCheck == null || !dalCheck.linked) {
+ // get_login_creds check failed, so try handle_all_urls check
+ if (dalCheckRequirement.equals(LoginOnly)) {
+ dalCheckDataCallback.onDataNotAvailable(
+ "DAL: Login creds check failed.");
+ } else if (dalCheckRequirement.equals(AllUrls)) {
+ mDalService.check(webDomain, PERMISSION_HANDLE_ALL_URLS,
+ packageName, fingerprint).enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call,
+ @NonNull Response response) {
+ DalCheck dalCheck = response.body();
+ mCache.put(dalInfo, dalCheck);
+ dalCheckDataCallback.onLoaded(dalCheck);
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call,
+ @NonNull Throwable t) {
+ dalCheckDataCallback.onDataNotAvailable(t.getMessage());
+ }
+ });
+ }
+ } else {
+ // get_login_creds check succeeded, so we're finished.
+ mCache.put(dalInfo, dalCheck);
+ dalCheckDataCallback.onLoaded(dalCheck);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ // get_login_creds check failed, so try handle_all_urls check.
+ mDalService.check(webDomain, PERMISSION_HANDLE_ALL_URLS, packageName,
+ fingerprint);
+ }
+ });
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..e58e18b8db4bdc9301d28eb0fcf3413256ace416
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2017 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.data.source.local;
+
+import android.content.SharedPreferences;
+import android.service.autofill.Dataset;
+
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.source.AutofillDataSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.AutofillHint;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.model.ResourceIdHeuristic;
+import com.example.android.autofill.service.util.AppExecutors;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.example.android.autofill.service.util.Util.logw;
+
+public class LocalAutofillDataSource implements AutofillDataSource {
+ public static final String SHARED_PREF_KEY = "com.example.android.autofill"
+ + ".service.datasource.LocalAutofillDataSource";
+ private static final String DATASET_NUMBER_KEY = "datasetNumber";
+ private static final Object sLock = new Object();
+
+ private static LocalAutofillDataSource sInstance;
+
+ private final AutofillDao mAutofillDao;
+ private final SharedPreferences mSharedPreferences;
+ private final AppExecutors mAppExecutors;
+
+ private LocalAutofillDataSource(SharedPreferences sharedPreferences, AutofillDao autofillDao,
+ AppExecutors appExecutors) {
+ mSharedPreferences = sharedPreferences;
+ mAutofillDao = autofillDao;
+ mAppExecutors = appExecutors;
+ }
+
+ public static LocalAutofillDataSource getInstance(SharedPreferences sharedPreferences,
+ AutofillDao autofillDao, AppExecutors appExecutors) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new LocalAutofillDataSource(sharedPreferences, autofillDao,
+ appExecutors);
+ }
+ return sInstance;
+ }
+ }
+
+ public static void clearInstance() {
+ synchronized (sLock) {
+ sInstance = null;
+ }
+ }
+
+ @Override
+ public void getAutofillDatasets(List allAutofillHints,
+ DataCallback> datasetsCallback) {
+ mAppExecutors.diskIO().execute(() -> {
+ final List typeNames = getFieldTypesForAutofillHints(allAutofillHints)
+ .stream()
+ .map(FieldTypeWithHeuristics::getFieldType)
+ .map(FieldType::getTypeName)
+ .collect(Collectors.toList());
+ List datasetsWithFilledAutofillFields =
+ mAutofillDao.getDatasets(typeNames);
+ mAppExecutors.mainThread().execute(() ->
+ datasetsCallback.onLoaded(datasetsWithFilledAutofillFields)
+ );
+ });
+ }
+
+ @Override
+ public void getAllAutofillDatasets(
+ DataCallback> datasetsCallback) {
+ mAppExecutors.diskIO().execute(() -> {
+ List datasetsWithFilledAutofillFields =
+ mAutofillDao.getAllDatasets();
+ mAppExecutors.mainThread().execute(() ->
+ datasetsCallback.onLoaded(datasetsWithFilledAutofillFields)
+ );
+ });
+ }
+
+ @Override
+ public void getAutofillDataset(List allAutofillHints, String datasetName,
+ DataCallback datasetsCallback) {
+ mAppExecutors.diskIO().execute(() -> {
+ // Room does not support TypeConverters for collections.
+ List autofillDatasetFields =
+ mAutofillDao.getDatasetsWithName(allAutofillHints, datasetName);
+ if (autofillDatasetFields != null && !autofillDatasetFields.isEmpty()) {
+ if (autofillDatasetFields.size() > 1) {
+ logw("More than 1 dataset with name %s", datasetName);
+ }
+ DatasetWithFilledAutofillFields dataset = autofillDatasetFields.get(0);
+
+ mAppExecutors.mainThread().execute(() ->
+ datasetsCallback.onLoaded(dataset)
+ );
+ } else {
+ mAppExecutors.mainThread().execute(() ->
+ datasetsCallback.onDataNotAvailable("No data found.")
+ );
+ }
+ });
+ }
+
+
+ @Override
+ public void saveAutofillDatasets(List
+ datasetsWithFilledAutofillFields) {
+ mAppExecutors.diskIO().execute(() -> {
+ for (DatasetWithFilledAutofillFields datasetWithFilledAutofillFields :
+ datasetsWithFilledAutofillFields) {
+ List filledAutofillFields =
+ datasetWithFilledAutofillFields.filledAutofillFields;
+ AutofillDataset autofillDataset = datasetWithFilledAutofillFields.autofillDataset;
+ mAutofillDao.insertAutofillDataset(autofillDataset);
+ mAutofillDao.insertFilledAutofillFields(filledAutofillFields);
+ }
+ });
+ incrementDatasetNumber();
+ }
+
+ @Override
+ public void saveResourceIdHeuristic(ResourceIdHeuristic resourceIdHeuristic) {
+ mAppExecutors.diskIO().execute(() -> {
+ mAutofillDao.insertResourceIdHeuristic(resourceIdHeuristic);
+ });
+ }
+
+ @Override
+ public void getFieldTypes(DataCallback> fieldTypesCallback) {
+ mAppExecutors.diskIO().execute(() -> {
+ List fieldTypeWithHints = mAutofillDao.getFieldTypesWithHints();
+ mAppExecutors.mainThread().execute(() -> {
+ if (fieldTypeWithHints != null) {
+ fieldTypesCallback.onLoaded(fieldTypeWithHints);
+ } else {
+ fieldTypesCallback.onDataNotAvailable("Field Types not found.");
+ }
+ });
+ });
+ }
+
+ @Override
+ public void getFieldTypeByAutofillHints(
+ DataCallback> fieldTypeMapCallback) {
+ mAppExecutors.diskIO().execute(() -> {
+ HashMap hintMap = getFieldTypeByAutofillHints();
+ mAppExecutors.mainThread().execute(() -> {
+ if (hintMap != null) {
+ fieldTypeMapCallback.onLoaded(hintMap);
+ } else {
+ fieldTypeMapCallback.onDataNotAvailable("FieldTypes not found");
+ }
+ });
+ });
+ }
+
+ @Override
+ public void getFilledAutofillField(String datasetId, String fieldTypeName, DataCallback fieldCallback) {
+ mAppExecutors.diskIO().execute(() -> {
+ FilledAutofillField filledAutofillField = mAutofillDao.getFilledAutofillField(datasetId, fieldTypeName);
+ mAppExecutors.mainThread().execute(() -> {
+ fieldCallback.onLoaded(filledAutofillField);
+ });
+ });
+ }
+
+ @Override
+ public void getFieldType(String fieldTypeName, DataCallback fieldTypeCallback) {
+ mAppExecutors.diskIO().execute(() -> {
+ FieldType fieldType = mAutofillDao.getFieldType(fieldTypeName);
+ mAppExecutors.mainThread().execute(() -> {
+ fieldTypeCallback.onLoaded(fieldType);
+ });
+ });
+ }
+
+ public void getAutofillDatasetWithId(String datasetId,
+ DataCallback callback) {
+ mAppExecutors.diskIO().execute(() -> {
+ DatasetWithFilledAutofillFields dataset =
+ mAutofillDao.getAutofillDatasetWithId(datasetId);
+ mAppExecutors.mainThread().execute(() -> {
+ callback.onLoaded(dataset);
+ });
+ });
+ }
+
+ private HashMap getFieldTypeByAutofillHints() {
+ HashMap hintMap = new HashMap<>();
+ List fieldTypeWithHints =
+ mAutofillDao.getFieldTypesWithHints();
+ if (fieldTypeWithHints != null) {
+ for (FieldTypeWithHeuristics fieldType : fieldTypeWithHints) {
+ for (AutofillHint hint : fieldType.autofillHints) {
+ hintMap.put(hint.mAutofillHint, fieldType);
+ }
+ }
+ return hintMap;
+ } else {
+ return null;
+ }
+ }
+
+ private List getFieldTypesForAutofillHints(List autofillHints) {
+ return mAutofillDao.getFieldTypesForAutofillHints(autofillHints);
+ }
+
+ @Override
+ public void clear() {
+ mAppExecutors.diskIO().execute(() -> {
+ mAutofillDao.clearAll();
+ mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, 0).apply();
+ });
+ }
+
+ /**
+ * For simplicity, {@link Dataset}s will be named in the form {@code dataset-X.P} where
+ * {@code X} means this was the Xth group of datasets saved, and {@code P} refers to the dataset
+ * partition number. This method returns the appropriate {@code X}.
+ */
+ public int getDatasetNumber() {
+ return mSharedPreferences.getInt(DATASET_NUMBER_KEY, 0);
+ }
+
+ /**
+ * Every time a dataset is saved, this should be called to increment the dataset number.
+ * (only important for this service's dataset naming scheme).
+ */
+ private void incrementDatasetNumber() {
+ mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber() + 1).apply();
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/SharedPrefsPackageVerificationRepository.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/SharedPrefsPackageVerificationRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..0d43c1fe09ef949657aacc3c7e5a0172fd715fbd
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/SharedPrefsPackageVerificationRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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.data.source.local;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import com.example.android.autofill.service.data.source.PackageVerificationDataSource;
+import com.example.android.autofill.service.util.SecurityHelper;
+
+import static com.example.android.autofill.service.util.Util.logd;
+import static com.example.android.autofill.service.util.Util.logw;
+
+public class SharedPrefsPackageVerificationRepository implements PackageVerificationDataSource {
+
+ private static final String SHARED_PREF_KEY = "com.example.android.autofill.service"
+ + ".datasource.PackageVerificationDataSource";
+ private static PackageVerificationDataSource sInstance;
+
+ private final SharedPreferences mSharedPrefs;
+ private final Context mContext;
+
+ private SharedPrefsPackageVerificationRepository(Context context) {
+ mSharedPrefs = context.getApplicationContext()
+ .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE);
+ mContext = context.getApplicationContext();
+ }
+
+ public static PackageVerificationDataSource getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new SharedPrefsPackageVerificationRepository(
+ context.getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ @Override
+ public void clear() {
+ mSharedPrefs.edit().clear().apply();
+ }
+
+ @Override
+ public boolean putPackageSignatures(String packageName) {
+ String hash;
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ hash = SecurityHelper.getFingerprint(packageInfo, packageName);
+ logd("Hash for %s: %s", packageName, hash);
+ } catch (Exception e) {
+ logw(e, "Error getting hash for %s.", packageName);
+ return false;
+ }
+
+ if (!containsSignatureForPackage(packageName)) {
+ // Storage does not yet contain signature for this package name.
+ mSharedPrefs.edit().putString(packageName, hash).apply();
+ return true;
+ }
+ return containsMatchingSignatureForPackage(packageName, hash);
+ }
+
+ private boolean containsSignatureForPackage(String packageName) {
+ return mSharedPrefs.contains(packageName);
+ }
+
+ private boolean containsMatchingSignatureForPackage(String packageName,
+ String hash) {
+ return hash.equals(mSharedPrefs.getString(packageName, null));
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..084532ce8f13621c2eddff096b1fa78d0d3dd0cb
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 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.data.source.local.dao;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.OnConflictStrategy;
+import android.arch.persistence.room.Query;
+
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.AutofillHint;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.model.ResourceIdHeuristic;
+
+import java.util.Collection;
+import java.util.List;
+
+@Dao
+public interface AutofillDao {
+ /**
+ * Fetches a list of datasets associated to autofill fields on the page.
+ *
+ * @param allAutofillHints Filtering parameter; represents all of the hints associated with
+ * all of the views on the page.
+ */
+ @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
+ " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
+ " AND FilledAutofillField.fieldTypeName IN (:allAutofillHints)")
+ List getDatasets(List allAutofillHints);
+
+ @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
+ " WHERE AutofillDataset.id = FilledAutofillField.datasetId")
+ List getAllDatasets();
+
+ /**
+ * Fetches a list of datasets associated to autofill fields. It should only return a dataset
+ * if that dataset has an autofill field associate with the view the user is focused on, and
+ * if that dataset's name matches the name passed in.
+ *
+ * @param fieldTypes Filtering parameter; represents all of the field types associated with
+ * all of the views on the page.
+ * @param datasetName Filtering parameter; only return datasets with this name.
+ */
+ @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
+ " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
+ " AND AutofillDataset.datasetName = (:datasetName)" +
+ " AND FilledAutofillField.fieldTypeName IN (:fieldTypes)")
+ List getDatasetsWithName(
+ List fieldTypes, String datasetName);
+
+ @Query("SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
+ "textTemplate, dateTemplate" +
+ " FROM FieldType, AutofillHint" +
+ " WHERE FieldType.typeName = AutofillHint.fieldTypeName" +
+ " UNION " +
+ "SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
+ "textTemplate, dateTemplate" +
+ " FROM FieldType, ResourceIdHeuristic" +
+ " WHERE FieldType.typeName = ResourceIdHeuristic.fieldTypeName")
+ List getFieldTypesWithHints();
+
+ @Query("SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
+ "textTemplate, dateTemplate" +
+ " FROM FieldType, AutofillHint" +
+ " WHERE FieldType.typeName = AutofillHint.fieldTypeName" +
+ " AND AutofillHint.autofillHint IN (:autofillHints)" +
+ " UNION " +
+ "SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
+ "textTemplate, dateTemplate" +
+ " FROM FieldType, ResourceIdHeuristic" +
+ " WHERE FieldType.typeName = ResourceIdHeuristic.fieldTypeName")
+ List getFieldTypesForAutofillHints(List autofillHints);
+
+ @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
+ " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
+ " AND AutofillDataset.id = (:datasetId)")
+ DatasetWithFilledAutofillFields getAutofillDatasetWithId(String datasetId);
+
+ @Query("SELECT * FROM FilledAutofillField" +
+ " WHERE FilledAutofillField.datasetId = (:datasetId)" +
+ " AND FilledAutofillField.fieldTypeName = (:fieldTypeName)")
+ FilledAutofillField getFilledAutofillField(String datasetId, String fieldTypeName);
+
+ @Query("SELECT * FROM FieldType" +
+ " WHERE FieldType.typeName = (:fieldTypeName)")
+ FieldType getFieldType(String fieldTypeName);
+
+ /**
+ * @param autofillFields Collection of autofill fields to be saved to the db.
+ */
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insertFilledAutofillFields(Collection autofillFields);
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insertAutofillDataset(AutofillDataset datasets);
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insertAutofillHints(List autofillHints);
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insertResourceIdHeuristic(ResourceIdHeuristic resourceIdHeuristic);
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insertFieldTypes(List fieldTypes);
+
+
+ @Query("DELETE FROM AutofillDataset")
+ void clearAll();
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/AutofillDatabase.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/AutofillDatabase.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe007ced9186faf977caa3897cf6fc0570026dca
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/AutofillDatabase.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 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.data.source.local.db;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.Database;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.RoomDatabase;
+import android.arch.persistence.room.TypeConverters;
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.model.AutofillDataset;
+import com.example.android.autofill.service.model.AutofillHint;
+import com.example.android.autofill.service.model.DefaultFieldTypeWithHints;
+import com.example.android.autofill.service.model.FakeData;
+import com.example.android.autofill.service.model.FieldType;
+import com.example.android.autofill.service.model.FilledAutofillField;
+import com.example.android.autofill.service.model.ResourceIdHeuristic;
+import com.example.android.autofill.service.util.AppExecutors;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.example.android.autofill.service.data.source.local.db.Converters.IntList;
+import static java.util.stream.Collectors.toList;
+
+@Database(entities = {
+ FilledAutofillField.class,
+ AutofillDataset.class,
+ FieldType.class,
+ AutofillHint.class,
+ ResourceIdHeuristic.class
+}, version = 1)
+@TypeConverters({Converters.class})
+public abstract class AutofillDatabase extends RoomDatabase {
+
+ private static final Object sLock = new Object();
+ private static AutofillDatabase sInstance;
+
+ public static AutofillDatabase getInstance(Context context,
+ DefaultFieldTypesSource defaultFieldTypesSource,
+ AppExecutors appExecutors) {
+ if (sInstance == null) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = Room.databaseBuilder(context.getApplicationContext(),
+ AutofillDatabase.class, "AutofillSample.db")
+ .addCallback(new RoomDatabase.Callback() {
+ @Override
+ public void onCreate(@NonNull SupportSQLiteDatabase db) {
+ appExecutors.diskIO().execute(() -> {
+ List fieldTypes =
+ defaultFieldTypesSource.getDefaultFieldTypes();
+ AutofillDatabase autofillDatabase =
+ getInstance(context, defaultFieldTypesSource,
+ appExecutors);
+ autofillDatabase.saveDefaultFieldTypes(fieldTypes);
+ });
+ }
+
+ @Override
+ public void onOpen(@NonNull SupportSQLiteDatabase db) {
+ super.onOpen(db);
+ }
+ })
+ .build();
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ private void saveDefaultFieldTypes(List defaultFieldTypes) {
+ List storedFieldTypes = new ArrayList<>();
+ List storedAutofillHints = new ArrayList<>();
+ for (DefaultFieldTypeWithHints defaultType : defaultFieldTypes) {
+ DefaultFieldTypeWithHints.DefaultFieldType defaultFieldType = defaultType.fieldType;
+ List autofillHints = defaultType.autofillHints;
+ IntList autofillTypes = new IntList(defaultFieldType.autofillTypes);
+ DefaultFieldTypeWithHints.DefaultFakeData defaultFakeData = defaultType.fieldType.fakeData;
+ FakeData fakeData = new FakeData(new Converters.StringList(
+ defaultFakeData.strictExampleSet), defaultFakeData.textTemplate,
+ defaultFakeData.dateTemplate);
+ FieldType storedFieldType = new FieldType(defaultFieldType.typeName, autofillTypes,
+ defaultFieldType.saveInfo, defaultFieldType.partition, fakeData);
+ storedFieldTypes.add(storedFieldType);
+ storedAutofillHints.addAll(autofillHints.stream()
+ .map((autofillHint) -> new AutofillHint(autofillHint,
+ storedFieldType.getTypeName())).collect(toList()));
+ }
+ AutofillDao autofillDao = autofillDao();
+ autofillDao.insertFieldTypes(storedFieldTypes);
+ autofillDao.insertAutofillHints(storedAutofillHints);
+ }
+
+ public abstract AutofillDao autofillDao();
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/Converters.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/Converters.java
new file mode 100644
index 0000000000000000000000000000000000000000..2829f8bd8ed4d49e553474bf8d8056140e9df591
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/Converters.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.data.source.local.db;
+
+import android.arch.persistence.room.TypeConverter;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Type converter for Room database.
+ */
+public class Converters {
+
+ /**
+ * If database returns a {@link String} containing a comma delimited list of ints, this converts
+ * the {@link String} to an {@link IntList}.
+ */
+ @TypeConverter
+ public static IntList storedStringToIntList(String value) {
+ List strings = Arrays.asList(value.split("\\s*,\\s*"));
+ List ints = strings.stream().map(Integer::parseInt).collect(toList());
+ return new IntList(ints);
+ }
+
+ /**
+ * Converts the {@link IntList} back into a String containing a comma delimited list of
+ * ints.
+ */
+ @TypeConverter
+ public static String intListToStoredString(IntList list) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (Integer integer : list.ints) {
+ stringBuilder.append(integer).append(",");
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * If database returns a {@link String} containing a comma delimited list of Strings, this
+ * converts the {@link String} to a {@link StringList}.
+ */
+ @TypeConverter
+ public static StringList storedStringToStringList(String value) {
+ List strings = Arrays.asList(value.split("\\s*,\\s*"));
+ return new StringList(strings);
+ }
+
+
+ /**
+ * Converts the {@link StringList} back into a {@link String} containing a comma delimited
+ * list of {@link String}s.
+ */
+ @TypeConverter
+ public static String stringListToStoredString(StringList list) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (String string : list.strings) {
+ stringBuilder.append(string).append(",");
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Wrapper class for {@code List} so it can work with Room type converters.
+ */
+ public static class IntList {
+ public final List ints;
+
+ public IntList(List ints) {
+ this.ints = ints;
+ }
+ }
+
+ /**
+ * Wrapper class for {@code List} so it can work with Room type converters.
+ */
+ public static class StringList {
+ public final List strings;
+
+ public StringList(List ints) {
+ this.strings = ints;
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b6596c83ff8a33e1a7ae4eaa92c21d9d2723253
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.support.annotation.NonNull;
+
+@Entity(primaryKeys = {"id"})
+public class AutofillDataset {
+ @NonNull
+ @ColumnInfo(name = "id")
+ private final String mId;
+
+ @NonNull
+ @ColumnInfo(name = "datasetName")
+ private final String mDatasetName;
+
+ @NonNull
+ @ColumnInfo(name = "packageName")
+ private final String mPackageName;
+
+ public AutofillDataset(@NonNull String id, @NonNull String datasetName,
+ @NonNull String packageName) {
+ mId = id;
+ mDatasetName = datasetName;
+ mPackageName = packageName;
+ }
+
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ @NonNull
+ public String getDatasetName() {
+ return mDatasetName;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AutofillDataset that = (AutofillDataset) o;
+
+ if (!mId.equals(that.mId)) return false;
+ if (!mDatasetName.equals(that.mDatasetName)) return false;
+ return mPackageName.equals(that.mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mId.hashCode();
+ result = 31 * result + mDatasetName.hashCode();
+ result = 31 * result + mPackageName.hashCode();
+ return result;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillHint.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillHint.java
new file mode 100644
index 0000000000000000000000000000000000000000..be4aa7d9726a32af7118f4bde5fb356c242885d3
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillHint.java
@@ -0,0 +1,41 @@
+/*
+ * 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.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.support.annotation.NonNull;
+
+@Entity(primaryKeys = {"autofillHint"}, foreignKeys = @ForeignKey(
+ entity = FieldType.class, parentColumns = "typeName", childColumns = "fieldTypeName",
+ onDelete = ForeignKey.CASCADE))
+public class AutofillHint {
+
+ @NonNull
+ @ColumnInfo(name = "autofillHint")
+ public String mAutofillHint;
+
+ @NonNull
+ @ColumnInfo(name = "fieldTypeName")
+ public String mFieldTypeName;
+
+ public AutofillHint(@NonNull String autofillHint, @NonNull String fieldTypeName) {
+ this.mAutofillHint = autofillHint;
+ this.mFieldTypeName = fieldTypeName;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalCheck.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalCheck.java
new file mode 100644
index 0000000000000000000000000000000000000000..ede77ba9cc99a1cf40caf97c0ae6c63e78ed8cf1
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalCheck.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 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.model;
+
+public class DalCheck {
+ public boolean linked;
+ public String maxAge;
+ public String debugString;
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..44002ca5ebf7913905b2e02c65036b5864a1005c
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 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.model;
+
+import static com.example.android.autofill.service.data.source.local.DigitalAssetLinksRepository.getCanonicalDomain;
+
+public class DalInfo {
+ private final String mWebDomain;
+ private final String mPackageName;
+
+ public DalInfo(String webDomain, String packageName) {
+ String canonicalDomain = getCanonicalDomain(webDomain);
+ final String fullDomain;
+ if (!webDomain.startsWith("http:") && !webDomain.startsWith("https:")) {
+ // Unfortunately AssistStructure.ViewNode does not tell what the domain is, so let's
+ // assume it's https
+ fullDomain = "https://" + canonicalDomain;
+ } else {
+ fullDomain = canonicalDomain;
+ }
+ mWebDomain = fullDomain;
+ mPackageName = packageName;
+ }
+
+ public String getWebDomain() {
+ return mWebDomain;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DalInfo dalInfo = (DalInfo) o;
+
+ if (mWebDomain != null ? !mWebDomain.equals(dalInfo.mWebDomain) :
+ dalInfo.mWebDomain != null)
+ return false;
+ return mPackageName != null ? mPackageName.equals(dalInfo.mPackageName) :
+ dalInfo.mPackageName == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mWebDomain != null ? mWebDomain.hashCode() : 0;
+ result = 31 * result + (mPackageName != null ? mPackageName.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java
new file mode 100644
index 0000000000000000000000000000000000000000..06b529fec346a1c50ae2b274b87d34e9aaf6fa54
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.model;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Relation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DatasetWithFilledAutofillFields {
+ @Embedded
+ public AutofillDataset autofillDataset;
+
+ @Relation(parentColumn = "id", entityColumn = "datasetId", entity = FilledAutofillField.class)
+ public List filledAutofillFields;
+
+ public void add(FilledAutofillField filledAutofillField) {
+ if (filledAutofillFields == null) {
+ this.filledAutofillFields = new ArrayList<>();
+ }
+ this.filledAutofillFields.add(filledAutofillField);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DatasetWithFilledAutofillFields that = (DatasetWithFilledAutofillFields) o;
+
+ if (autofillDataset != null ? !autofillDataset.equals(that.autofillDataset) :
+ that.autofillDataset != null)
+ return false;
+ return filledAutofillFields != null ?
+ filledAutofillFields.equals(that.filledAutofillFields) :
+ that.filledAutofillFields == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = autofillDataset != null ? autofillDataset.hashCode() : 0;
+ result = 31 * result + (filledAutofillFields != null ? filledAutofillFields.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DefaultFieldTypeWithHints.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DefaultFieldTypeWithHints.java
new file mode 100644
index 0000000000000000000000000000000000000000..70ad382e2e13cd08ef3d63953721845c6ec03aa0
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DefaultFieldTypeWithHints.java
@@ -0,0 +1,73 @@
+/*
+ * 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.model;
+
+import java.util.List;
+
+/**
+ * JSON model class, representing an autofillable field type. It is called "Default" because only
+ * default field types will be included in the packaged JSON. After the JSON is initially read and
+ * written to the DB, the field types can be dynamically added, modified, and removed.
+ *
+ * It contains all of the metadata about the field type. For example, if the field type is
+ * "country", this is the JSON object associated with it:
+
+ {
+ "autofillHints": [
+ "country"
+ ],
+ "fieldType": {
+ "autofillTypes": [
+ 1,
+ 3
+ ],
+ "fakeData": {
+ "strictExampleSet": [],
+ "textTemplate": "countryseed"
+ },
+ "partition": 1,
+ "saveInfo": 2,
+ "typeName": "country"
+ }
+ }
+
+ */
+public class DefaultFieldTypeWithHints {
+ public DefaultFieldType fieldType;
+ public List autofillHints;
+
+ public static class DefaultFieldType {
+ public String typeName;
+ public List autofillTypes;
+ public int saveInfo;
+ public int partition;
+ public DefaultFakeData fakeData;
+ }
+
+ public static class DefaultFakeData {
+ public List strictExampleSet;
+ public String textTemplate;
+ public String dateTemplate;
+
+ public DefaultFakeData(List strictExampleSet, String textTemplate,
+ String dateTemplate) {
+ this.strictExampleSet = strictExampleSet;
+ this.textTemplate = textTemplate;
+ this.dateTemplate = dateTemplate;
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FakeData.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FakeData.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d0837b2ca70a745bd64e0697489add60133ff17
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FakeData.java
@@ -0,0 +1,31 @@
+/*
+ * 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.model;
+
+import com.example.android.autofill.service.data.source.local.db.Converters;
+
+public class FakeData {
+ public Converters.StringList strictExampleSet;
+ public String textTemplate;
+ public String dateTemplate;
+
+ public FakeData(Converters.StringList strictExampleSet, String textTemplate, String dateTemplate) {
+ this.strictExampleSet = strictExampleSet;
+ this.textTemplate = textTemplate;
+ this.dateTemplate = dateTemplate;
+ }
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldType.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldType.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d285f349d742395165e96f95c50affa0b4d8cc9
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldType.java
@@ -0,0 +1,81 @@
+/*
+ * 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.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Entity;
+import android.support.annotation.NonNull;
+
+import static com.example.android.autofill.service.data.source.local.db.Converters.IntList;
+
+@Entity(primaryKeys = {"typeName"})
+public class FieldType {
+ @NonNull
+ @ColumnInfo(name = "typeName")
+ private final String mTypeName;
+
+ @NonNull
+ @ColumnInfo(name = "autofillTypes")
+ private final IntList mAutofillTypes;
+
+ @NonNull
+ @ColumnInfo(name = "saveInfo")
+ private final Integer mSaveInfo;
+
+ @NonNull
+ @ColumnInfo(name = "partition")
+ private final Integer mPartition;
+
+ @NonNull
+ @Embedded
+ private final FakeData mFakeData;
+
+ public FieldType(@NonNull String typeName, @NonNull IntList autofillTypes,
+ @NonNull Integer saveInfo, @NonNull Integer partition, @NonNull FakeData fakeData) {
+ mTypeName = typeName;
+ mAutofillTypes = autofillTypes;
+ mSaveInfo = saveInfo;
+ mPartition = partition;
+ mFakeData = fakeData;
+ }
+
+ @NonNull
+ public String getTypeName() {
+ return mTypeName;
+ }
+
+ @NonNull
+ public IntList getAutofillTypes() {
+ return mAutofillTypes;
+ }
+
+ @NonNull
+ public Integer getSaveInfo() {
+ return mSaveInfo;
+ }
+
+ @NonNull
+ public Integer getPartition() {
+ return mPartition;
+ }
+
+ @NonNull
+ public FakeData getFakeData() {
+ return mFakeData;
+ }
+}
\ No newline at end of file
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldTypeWithHeuristics.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldTypeWithHeuristics.java
new file mode 100644
index 0000000000000000000000000000000000000000..97a646e5715151129cfdf26d661cad1392aee9eb
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FieldTypeWithHeuristics.java
@@ -0,0 +1,45 @@
+/*
+ * 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.model;
+
+import android.arch.persistence.room.Embedded;
+import android.arch.persistence.room.Relation;
+
+import java.util.List;
+
+public class FieldTypeWithHeuristics {
+ @Embedded
+ public FieldType fieldType;
+
+ @Relation(parentColumn = "typeName", entityColumn = "fieldTypeName", entity = AutofillHint.class)
+ public List autofillHints;
+
+ @Relation(parentColumn = "typeName", entityColumn = "fieldTypeName", entity = ResourceIdHeuristic.class)
+ public List resourceIdHeuristics;
+
+ public FieldType getFieldType() {
+ return fieldType;
+ }
+
+ public List getAutofillHints() {
+ return autofillHints;
+ }
+
+ public List getResourceIdHeuristics() {
+ return resourceIdHeuristics;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillField.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillField.java
new file mode 100644
index 0000000000000000000000000000000000000000..85ad05ac73c1adcd89f580bb663c0cf362ff0899
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillField.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 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.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.arch.persistence.room.Ignore;
+import android.support.annotation.NonNull;
+
+import javax.annotation.Nullable;
+
+@Entity(primaryKeys = {"datasetId", "fieldTypeName"}, foreignKeys = {
+ @ForeignKey(entity = AutofillDataset.class, parentColumns = "id",
+ childColumns = "datasetId", onDelete = ForeignKey.CASCADE),
+ @ForeignKey(entity = FieldType.class, parentColumns = "typeName",
+ childColumns = "fieldTypeName", onDelete = ForeignKey.CASCADE)
+})
+public class FilledAutofillField {
+
+ @NonNull
+ @ColumnInfo(name = "datasetId")
+ private final String mDatasetId;
+
+ @Nullable
+ @ColumnInfo(name = "textValue")
+ private final String mTextValue;
+
+ @Nullable
+ @ColumnInfo(name = "dateValue")
+ private final Long mDateValue;
+
+ @Nullable
+ @ColumnInfo(name = "toggleValue")
+ private final Boolean mToggleValue;
+
+ @NonNull
+ @ColumnInfo(name = "fieldTypeName")
+ private final String mFieldTypeName;
+
+ public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName,
+ @Nullable String textValue, @Nullable Long dateValue,
+ @Nullable Boolean toggleValue) {
+ mDatasetId = datasetId;
+ mFieldTypeName = fieldTypeName;
+ mTextValue = textValue;
+ mDateValue = dateValue;
+ mToggleValue = toggleValue;
+ }
+
+ @Ignore
+ public FilledAutofillField(@NonNull String datasetId,
+ @NonNull String fieldTypeName, @Nullable String textValue, @Nullable Long dateValue) {
+ this(datasetId, fieldTypeName, textValue, dateValue, null);
+ }
+
+ @Ignore
+ public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName,
+ @Nullable String textValue) {
+ this(datasetId, fieldTypeName, textValue, null, null);
+ }
+
+ @Ignore
+ public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName,
+ @Nullable Long dateValue) {
+ this(datasetId, fieldTypeName, null, dateValue, null);
+ }
+
+ @Ignore
+ public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName,
+ @Nullable Boolean toggleValue) {
+ this(datasetId, fieldTypeName, null, null, toggleValue);
+ }
+
+ @Ignore
+ public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName) {
+ this(datasetId, fieldTypeName, null, null, null);
+ }
+
+ @NonNull
+ public String getDatasetId() {
+ return mDatasetId;
+ }
+
+ @Nullable
+ public String getTextValue() {
+ return mTextValue;
+ }
+
+ @Nullable
+ public Long getDateValue() {
+ return mDateValue;
+ }
+
+ @Nullable
+ public Boolean getToggleValue() {
+ return mToggleValue;
+ }
+
+ @NonNull
+ public String getFieldTypeName() {
+ return mFieldTypeName;
+ }
+
+ public boolean isNull() {
+ return mTextValue == null && mDateValue == null && mToggleValue == null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FilledAutofillField that = (FilledAutofillField) o;
+
+ if (mTextValue != null ? !mTextValue.equals(that.mTextValue) : that.mTextValue != null)
+ return false;
+ if (mDateValue != null ? !mDateValue.equals(that.mDateValue) : that.mDateValue != null)
+ return false;
+ if (mToggleValue != null ? !mToggleValue.equals(that.mToggleValue) : that.mToggleValue != null)
+ return false;
+ return mFieldTypeName.equals(that.mFieldTypeName);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mTextValue != null ? mTextValue.hashCode() : 0;
+ result = 31 * result + (mDateValue != null ? mDateValue.hashCode() : 0);
+ result = 31 * result + (mToggleValue != null ? mToggleValue.hashCode() : 0);
+ result = 31 * result + mFieldTypeName.hashCode();
+ return result;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/ResourceIdHeuristic.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/ResourceIdHeuristic.java
new file mode 100644
index 0000000000000000000000000000000000000000..81c9abef01e985206062c2a1269bc74ba94535d5
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/ResourceIdHeuristic.java
@@ -0,0 +1,47 @@
+/*
+ * 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.model;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.ForeignKey;
+import android.support.annotation.NonNull;
+
+@Entity(primaryKeys = {"resourceIdHeuristic", "packageName"}, foreignKeys = @ForeignKey(
+ entity = FieldType.class, parentColumns = "typeName", childColumns = "fieldTypeName",
+ onDelete = ForeignKey.CASCADE))
+public class ResourceIdHeuristic {
+
+ @NonNull
+ @ColumnInfo(name = "resourceIdHeuristic")
+ public String mResourceIdHeuristic;
+
+ @NonNull
+ @ColumnInfo(name = "packageName")
+ public String mPackageName;
+
+ @NonNull
+ @ColumnInfo(name = "fieldTypeName")
+ public String mFieldTypeName;
+
+ public ResourceIdHeuristic(@NonNull String resourceIdHeuristic, @NonNull String fieldTypeName,
+ @NonNull String packageName) {
+ mResourceIdHeuristic = resourceIdHeuristic;
+ mFieldTypeName = fieldTypeName;
+ mPackageName = packageName;
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/MyPreferences.java
similarity index 75%
rename from input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.java
rename to input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/MyPreferences.java
index bb6e831440a7588588a448fa59970752a766ca1d..8770ef8318a0d3550470b89d59483e547fce5518 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.java
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/MyPreferences.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.example.android.autofillframework.multidatasetservice.settings;
+package com.example.android.autofill.service.settings;
import android.content.Context;
import android.content.SharedPreferences;
@@ -21,13 +21,14 @@ import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.support.annotation.NonNull;
-public class MyPreferences {
- private static final String TAG = "MyPreferences";
+import com.example.android.autofill.service.util.Util;
+public class MyPreferences {
private static final String RESPONSE_AUTH_KEY = "response_auth";
private static final String DATASET_AUTH_KEY = "dataset_auth";
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 MyPreferences sInstance;
private final SharedPreferences mPrefs;
@@ -88,4 +89,22 @@ public class MyPreferences {
public void clearCredentials() {
mPrefs.edit().remove(MASTER_PASSWORD_KEY).apply();
}
+
+ public Util.LogLevel getLoggingLevel() {
+ return Util.LogLevel.values()[mPrefs.getInt(LOGGING_LEVEL, Util.LogLevel.Off.ordinal())];
+ }
+
+ public void setLoggingLevel(Util.LogLevel level) {
+ mPrefs.edit().putInt(LOGGING_LEVEL, level.ordinal()).apply();
+ Util.setLoggingLevel(level);
+ }
+
+ public Util.DalCheckRequirement getDalCheckRequirement() {
+ return Util.DalCheckRequirement.values()[mPrefs.getInt(DAL_CHECK_REQUIRED,
+ Util.DalCheckRequirement.AllUrls.ordinal())];
+ }
+
+ public void setDalCheckRequired(Util.DalCheckRequirement level) {
+ mPrefs.edit().putInt(DAL_CHECK_REQUIRED, level.ordinal()).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
new file mode 100644
index 0000000000000000000000000000000000000000..1fd87df0555ea5176de5e58ccee7596f52042d2b
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2017 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.settings;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.autofill.AutofillManager;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.NumberPicker;
+import android.widget.RadioGroup;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.example.android.autofill.service.R;
+import com.example.android.autofill.service.data.AutofillDataBuilder;
+import com.example.android.autofill.service.data.DataCallback;
+import com.example.android.autofill.service.data.FakeAutofillDataBuilder;
+import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
+import com.example.android.autofill.service.data.source.PackageVerificationDataSource;
+import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
+import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
+import com.example.android.autofill.service.data.source.local.SharedPrefsPackageVerificationRepository;
+import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
+import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
+import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
+import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
+import com.example.android.autofill.service.util.AppExecutors;
+import com.example.android.autofill.service.util.Util;
+import com.google.gson.GsonBuilder;
+
+import java.util.List;
+
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.AllUrls;
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.Disabled;
+import static com.example.android.autofill.service.util.Util.DalCheckRequirement.LoginOnly;
+import static com.example.android.autofill.service.util.Util.logd;
+import static com.example.android.autofill.service.util.Util.logw;
+
+public class SettingsActivity extends AppCompatActivity {
+ private static final String TAG = "SettingsActivity";
+ private static final int REQUEST_CODE_SET_DEFAULT = 1;
+ private AutofillManager mAutofillManager;
+ private LocalAutofillDataSource mLocalAutofillDataSource;
+ private PackageVerificationDataSource mPackageVerificationDataSource;
+ private MyPreferences mPreferences;
+ private String mPackageName;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.multidataset_service_settings_activity);
+ SharedPreferences localAfDataSourceSharedPrefs =
+ getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
+ DefaultFieldTypesSource defaultFieldTypesSource =
+ DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
+ new GsonBuilder().create());
+ AutofillDao autofillDao = AutofillDatabase.getInstance(
+ this, defaultFieldTypesSource, new AppExecutors()).autofillDao();
+ mPackageName = getPackageName();
+ mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(localAfDataSourceSharedPrefs,
+ autofillDao, new AppExecutors());
+ mAutofillManager = getSystemService(AutofillManager.class);
+ mPackageVerificationDataSource =
+ SharedPrefsPackageVerificationRepository.getInstance(this);
+ mPreferences = MyPreferences.getInstance(this);
+ setupSettingsSwitch(R.id.settings_auth_responses_container,
+ R.id.settings_auth_responses_label,
+ R.id.settings_auth_responses_switch,
+ mPreferences.isResponseAuth(),
+ (compoundButton, isResponseAuth) -> mPreferences.setResponseAuth(isResponseAuth));
+ setupSettingsSwitch(R.id.settings_auth_datasets_container,
+ R.id.settings_auth_datasets_label,
+ R.id.settings_auth_datasets_switch,
+ mPreferences.isDatasetAuth(),
+ (compoundButton, isDatasetAuth) -> mPreferences.setDatasetAuth(isDatasetAuth));
+ setupSettingsButton(R.id.settings_add_data_container,
+ R.id.settings_add_data_label,
+ R.id.settings_add_data_icon,
+ (view) -> buildAddDataDialog().show());
+ setupSettingsButton(R.id.settings_clear_data_container,
+ R.id.settings_clear_data_label,
+ R.id.settings_clear_data_icon,
+ (view) -> buildClearDataDialog().show());
+ setupSettingsButton(R.id.settings_auth_credentials_container,
+ R.id.settings_auth_credentials_label,
+ R.id.settings_auth_credentials_icon,
+ (view) -> {
+ if (mPreferences.getMasterPassword() != null) {
+ buildCurrentCredentialsDialog().show();
+ } else {
+ buildNewCredentialsDialog().show();
+ }
+ });
+ setupSettingsSwitch(R.id.settingsSetServiceContainer,
+ R.id.settingsSetServiceLabel,
+ R.id.settingsSetServiceSwitch,
+ mAutofillManager.hasEnabledAutofillServices(),
+ (compoundButton, serviceSet) -> setService(serviceSet));
+ RadioGroup loggingLevelContainer = findViewById(R.id.loggingLevelContainer);
+ Util.LogLevel loggingLevel = mPreferences.getLoggingLevel();
+ Util.setLoggingLevel(loggingLevel);
+ switch (loggingLevel) {
+ case Off:
+ loggingLevelContainer.check(R.id.loggingOff);
+ break;
+ case Debug:
+ loggingLevelContainer.check(R.id.loggingDebug);
+ break;
+ case Verbose:
+ loggingLevelContainer.check(R.id.loggingVerbose);
+ break;
+ }
+ loggingLevelContainer.setOnCheckedChangeListener((group, checkedId) -> {
+ switch (checkedId) {
+ case R.id.loggingOff:
+ mPreferences.setLoggingLevel(Util.LogLevel.Off);
+ break;
+ case R.id.loggingDebug:
+ mPreferences.setLoggingLevel(Util.LogLevel.Debug);
+ break;
+ case R.id.loggingVerbose:
+ mPreferences.setLoggingLevel(Util.LogLevel.Verbose);
+ break;
+ }
+ });
+ RadioGroup dalCheckRequirementContainer = findViewById(R.id.dalCheckRequirementContainer);
+ Util.DalCheckRequirement dalCheckRequirement = mPreferences.getDalCheckRequirement();
+ switch (dalCheckRequirement) {
+ case Disabled:
+ dalCheckRequirementContainer.check(R.id.dalDisabled);
+ break;
+ case LoginOnly:
+ dalCheckRequirementContainer.check(R.id.dalLoginOnly);
+ break;
+ case AllUrls:
+ dalCheckRequirementContainer.check(R.id.dalAllUrls);
+ break;
+ }
+ dalCheckRequirementContainer.setOnCheckedChangeListener((group, checkedId) -> {
+ switch (checkedId) {
+ case R.id.dalDisabled:
+ mPreferences.setDalCheckRequired(Disabled);
+ break;
+ case R.id.dalLoginOnly:
+ mPreferences.setDalCheckRequired(LoginOnly);
+ break;
+ case R.id.dalAllUrls:
+ mPreferences.setDalCheckRequired(AllUrls);
+ break;
+ }
+ });
+ }
+
+ private AlertDialog buildClearDataDialog() {
+ return new AlertDialog.Builder(SettingsActivity.this)
+ .setMessage(R.string.settings_clear_data_confirmation)
+ .setTitle(R.string.settings_clear_data_confirmation_title)
+ .setNegativeButton(R.string.settings_cancel, null)
+ .setPositiveButton(R.string.settings_ok, (dialog, which) -> {
+ mLocalAutofillDataSource.clear();
+ mPackageVerificationDataSource.clear();
+ mPreferences.clearCredentials();
+ dialog.dismiss();
+ })
+ .create();
+ }
+
+ private AlertDialog buildAddDataDialog() {
+ NumberPicker numberOfDatasetsPicker = LayoutInflater
+ .from(SettingsActivity.this)
+ .inflate(R.layout.multidataset_service_settings_add_data_dialog, null)
+ .findViewById(R.id.number_of_datasets_picker);
+ numberOfDatasetsPicker.setMinValue(0);
+ numberOfDatasetsPicker.setMaxValue(10);
+ numberOfDatasetsPicker.setWrapSelectorWheel(false);
+ return new AlertDialog.Builder(SettingsActivity.this)
+ .setTitle(R.string.settings_add_data_title)
+ .setNegativeButton(R.string.settings_cancel, null)
+ .setMessage(R.string.settings_select_number_of_datasets)
+ .setView(numberOfDatasetsPicker)
+ .setPositiveButton(R.string.settings_ok, (dialog, which) -> {
+ int numOfDatasets = numberOfDatasetsPicker.getValue();
+ mLocalAutofillDataSource.getFieldTypes(new DataCallback>() {
+ @Override
+ public void onLoaded(List fieldTypes) {
+ boolean saved = buildAndSaveMockedAutofillFieldCollections(
+ fieldTypes, numOfDatasets);
+ dialog.dismiss();
+ if (saved) {
+ Snackbar.make(findViewById(R.id.settings_layout),
+ getResources().getQuantityString(
+ R.plurals.settings_add_data_success,
+ numOfDatasets, numOfDatasets),
+ Snackbar.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public void onDataNotAvailable(String msg, Object... params) {
+
+ }
+ });
+ })
+ .create();
+ }
+
+ public boolean buildAndSaveMockedAutofillFieldCollections(List fieldTypes,
+ int numOfDatasets) {
+ if (numOfDatasets < 0 || numOfDatasets > 10) {
+ logw("Number of Datasets (%d) out of range.", numOfDatasets);
+ }
+ for (int i = 0; i < numOfDatasets; i++) {
+ int datasetNumber = mLocalAutofillDataSource.getDatasetNumber();
+ AutofillDataBuilder autofillDataBuilder =
+ new FakeAutofillDataBuilder(fieldTypes, mPackageName, datasetNumber);
+ List datasetsWithFilledAutofillFields =
+ autofillDataBuilder.buildDatasetsByPartition(datasetNumber);
+ // Save datasets to database.
+ mLocalAutofillDataSource.saveAutofillDatasets(datasetsWithFilledAutofillFields);
+ }
+ return true;
+ }
+
+ private AlertDialog.Builder prepareCredentialsDialog() {
+ return new AlertDialog.Builder(SettingsActivity.this)
+ .setTitle(R.string.settings_auth_change_credentials_title)
+ .setNegativeButton(R.string.settings_cancel, null);
+ }
+
+ private AlertDialog buildCurrentCredentialsDialog() {
+ final EditText currentPasswordField = LayoutInflater
+ .from(SettingsActivity.this)
+ .inflate(R.layout.multidataset_service_settings_authentication_dialog, null)
+ .findViewById(R.id.master_password_field);
+ return prepareCredentialsDialog()
+ .setMessage(R.string.settings_auth_enter_current_password)
+ .setView(currentPasswordField)
+ .setPositiveButton(R.string.settings_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String password = currentPasswordField.getText().toString();
+ if (mPreferences.getMasterPassword()
+ .equals(password)) {
+ buildNewCredentialsDialog().show();
+ dialog.dismiss();
+ }
+ }
+ })
+ .create();
+ }
+
+ private AlertDialog buildNewCredentialsDialog() {
+ final EditText newPasswordField = LayoutInflater
+ .from(SettingsActivity.this)
+ .inflate(R.layout.multidataset_service_settings_authentication_dialog, null)
+ .findViewById(R.id.master_password_field);
+ return prepareCredentialsDialog()
+ .setMessage(R.string.settings_auth_enter_new_password)
+ .setView(newPasswordField)
+ .setPositiveButton(R.string.settings_ok, (dialog, which) -> {
+ String password = newPasswordField.getText().toString();
+ mPreferences.setMasterPassword(password);
+ dialog.dismiss();
+ })
+ .create();
+ }
+
+ private void setupSettingsSwitch(int containerId, int labelId, int switchId, boolean checked,
+ CompoundButton.OnCheckedChangeListener checkedChangeListener) {
+ ViewGroup container = findViewById(containerId);
+ String switchLabel = ((TextView) container.findViewById(labelId)).getText().toString();
+ final Switch switchView = container.findViewById(switchId);
+ switchView.setContentDescription(switchLabel);
+ switchView.setChecked(checked);
+ container.setOnClickListener((view) -> switchView.performClick());
+ switchView.setOnCheckedChangeListener(checkedChangeListener);
+ }
+
+ private void setupSettingsButton(int containerId, int labelId, int imageViewId,
+ final View.OnClickListener onClickListener) {
+ ViewGroup container = findViewById(containerId);
+ TextView buttonLabel = container.findViewById(labelId);
+ String buttonLabelText = buttonLabel.getText().toString();
+ ImageView imageView = container.findViewById(imageViewId);
+ imageView.setContentDescription(buttonLabelText);
+ container.setOnClickListener(onClickListener);
+ }
+
+ private void setService(boolean enableService) {
+ if (enableService) {
+ startEnableService();
+ } else {
+ disableService();
+ }
+ }
+
+ private void disableService() {
+ if (mAutofillManager != null && mAutofillManager.hasEnabledAutofillServices()) {
+ mAutofillManager.disableAutofillServices();
+ Snackbar.make(findViewById(R.id.settings_layout),
+ R.string.settings_autofill_disabled_message, Snackbar.LENGTH_SHORT).show();
+ } else {
+ logd("Sample service already disabled.");
+ }
+ }
+
+ private void startEnableService() {
+ if (mAutofillManager != null && !mAutofillManager.hasEnabledAutofillServices()) {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE);
+ intent.setData(Uri.parse("package:com.example.android.autofill.service"));
+ logd(TAG, "enableService(): intent=%s", intent);
+ startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT);
+ } else {
+ logd("Sample service already enabled.");
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ logd(TAG, "onActivityResult(): req=%s", requestCode);
+ switch (requestCode) {
+ case REQUEST_CODE_SET_DEFAULT:
+ onDefaultServiceSet(resultCode);
+ break;
+ }
+ }
+
+ private void onDefaultServiceSet(int resultCode) {
+ logd(TAG, "resultCode=%d", resultCode);
+ switch (resultCode) {
+ case RESULT_OK:
+ logd("Autofill service set.");
+ Snackbar.make(findViewById(R.id.settings_layout),
+ R.string.settings_autofill_service_set, Snackbar.LENGTH_SHORT)
+ .show();
+ break;
+ case RESULT_CANCELED:
+ logd("Autofill service not selected.");
+ Snackbar.make(findViewById(R.id.settings_layout),
+ R.string.settings_autofill_service_not_set, Snackbar.LENGTH_SHORT)
+ .show();
+ break;
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
new file mode 100644
index 0000000000000000000000000000000000000000..843440c9519b2d504dafd8b3f953a7cf7591c4ba
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
@@ -0,0 +1,105 @@
+/*
+ * 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 android.app.assist.AssistStructure.ViewNode;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import com.example.android.autofill.service.MyAutofillService;
+
+/**
+ * A basic service that uses some rudimentary heuristics to identify fields that are not explicitly
+ * marked with autofill hints.
+ *
+ * The goal of this class is to provide a simple autofill service implementation that is easy
+ * to understand and extend, but it should not 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}. *
+ */
+public class BasicHeuristicsService extends BasicService {
+
+ private static final String TAG = "BasicHeuristicsService";
+
+ @Override
+ @Nullable
+ protected String getHint(@NonNull ViewNode node) {
+
+ // First try the explicit autofill hints...
+
+ String hint = super.getHint(node);
+ if (hint != null) return hint;
+
+ // Then try some rudimentary heuristics based on other node properties
+
+ String viewHint = node.getHint();
+ hint = inferHint(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(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(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(@Nullable String string) {
+ if (string == null) return null;
+
+ string = string.toLowerCase();
+ 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;
+
+ return null;
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
new file mode 100644
index 0000000000000000000000000000000000000000..92d6436f18779959278659bbc8fefa8bcac2eb9e
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java
@@ -0,0 +1,213 @@
+/*
+ * 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 android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+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.support.v4.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.R;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A very basic {@link AutofillService} implementation that only shows dynamic-generated datasets
+ * and don't persist the saved data.
+ *
+ *
The goal of this class is to provide a simple autofill service implementation that is easy
+ * to understand and extend, but it should not 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}.
+ */
+public class BasicService extends AutofillService {
+
+ private static final String TAG = "BasicService";
+
+ /**
+ * Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA!
+ */
+ private static final int NUMBER_DATASETS = 4;
+
+ @Override
+ public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+ FillCallback callback) {
+ Log.d(TAG, "onFillRequest()");
+
+ // Find autofillable fields
+ AssistStructure structure = getLatestAssistStructure(request);
+ Map fields = getAutofillableFields(structure);
+ Log.d(TAG, "autofillable fields:" + fields);
+
+ if (fields.isEmpty()) {
+ toast("No autofill hints found");
+ callback.onSuccess(null);
+ return;
+ }
+
+ // Create the base response
+ FillResponse.Builder response = new FillResponse.Builder();
+
+ // 1.Add the dynamic datasets
+ String packageName = getApplicationContext().getPackageName();
+ for (int i = 1; i <= NUMBER_DATASETS; i++) {
+ Dataset.Builder dataset = new Dataset.Builder();
+ for (Entry field : fields.entrySet()) {
+ String hint = field.getKey();
+ AutofillId id = field.getValue();
+ String value = hint + i;
+ // We're simple - our dataset values are hardcoded as "hintN" (for example,
+ // "username1", "username2") 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);
+ }
+ response.addDataset(dataset.build());
+ }
+
+ // 2.Add save info
+ Collection 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!
+ callback.onSuccess(response.build());
+ }
+
+ @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.
+ *
+ * An autofillable field is a {@link ViewNode} whose {@link #getHint(ViewNode)} metho
+ */
+ @NonNull
+ private Map getAutofillableFields(@NonNull AssistStructure structure) {
+ Map 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 fields,
+ @NonNull ViewNode node) {
+ int type = node.getAutofillType();
+ // We're simple, we just autofill text fields.
+ if (type == View.AUTOFILL_TYPE_TEXT) {
+ 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));
+ }
+ }
+
+ /**
+ * Gets the autofill hint associated with the given node.
+ *
+ * By default it just return the first entry on the node's
+ * {@link ViewNode#getAutofillHints() autofillHints} (when available), but subclasses could
+ * extend it to use heuristics when the app developer didn't explicitly provide these hints.
+ *
+ */
+ @Nullable
+ protected String getHint(@NonNull ViewNode node) {
+ String[] hints = node.getAutofillHints();
+ if (hints == null) return null;
+
+ // We're simple, we only care about the first hint
+ String hint = hints[0].toLowerCase();
+ return hint;
+ }
+
+ /**
+ * Helper method to get the {@link AssistStructure} associated with the latest request
+ * in an autofill context.
+ */
+ @NonNull
+ private static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) {
+ List fillContexts = request.getFillContexts();
+ return fillContexts.get(fillContexts.size() - 1).getStructure();
+ }
+
+ /**
+ * Helper method to create a dataset presentation with the given text.
+ */
+ @NonNull
+ private static RemoteViews newDatasetPresentation(@NonNull String packageName,
+ @NonNull CharSequence text) {
+ RemoteViews presentation =
+ new RemoteViews(packageName, R.layout.multidataset_service_list_item);
+ presentation.setTextViewText(R.id.text, text);
+ presentation.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);
+ return presentation;
+ }
+
+ /**
+ * 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/util/AppExecutors.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/AppExecutors.java
new file mode 100644
index 0000000000000000000000000000000000000000..9befaebfa5908c783bf9205e3d2bc3cfa9539666
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/AppExecutors.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Global executor pools for the whole application.
+ *
+ * Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind
+ * webservice requests).
+ */
+public class AppExecutors {
+
+ private static final int THREAD_COUNT = 3;
+
+ private final Executor diskIO;
+
+ private final Executor networkIO;
+
+ private final Executor mainThread;
+
+ @VisibleForTesting
+ AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
+ this.diskIO = diskIO;
+ this.networkIO = networkIO;
+ this.mainThread = mainThread;
+ }
+
+ public AppExecutors() {
+ this(new DiskIOThreadExecutor(), Executors.newFixedThreadPool(THREAD_COUNT),
+ new MainThreadExecutor());
+ }
+
+ public Executor diskIO() {
+ return diskIO;
+ }
+
+ public Executor networkIO() {
+ return networkIO;
+ }
+
+ public Executor mainThread() {
+ return mainThread;
+ }
+
+ private static class MainThreadExecutor implements Executor {
+ private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void execute(@NonNull Runnable command) {
+ mainThreadHandler.post(command);
+ }
+ }
+
+ /**
+ * Executor that runs a task on a new background thread.
+ */
+ private static class DiskIOThreadExecutor implements Executor {
+
+ private final Executor mDiskIO;
+
+ public DiskIOThreadExecutor() {
+ mDiskIO = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ public void execute(@NonNull Runnable command) {
+ mDiskIO.execute(command);
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/SecurityHelper.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/SecurityHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..5311a5f9f1b318537fa8793ba2ba93c0393b8856
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/SecurityHelper.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+/**
+ * Helper class for security checks.
+ */
+public final class SecurityHelper {
+
+ private SecurityHelper() {
+ throw new UnsupportedOperationException("Provides static methods only.");
+ }
+
+ /**
+ * Gets the fingerprint of the signed certificate of a package.
+ */
+ public static String getFingerprint(PackageInfo packageInfo, String packageName) throws
+ PackageManager.NameNotFoundException, IOException, NoSuchAlgorithmException,
+ CertificateException {
+ Signature[] signatures = packageInfo.signatures;
+ if (signatures.length != 1) {
+ throw new SecurityException(packageName + " has " + signatures.length + " signatures");
+ }
+ byte[] cert = signatures[0].toByteArray();
+ try (InputStream input = new ByteArrayInputStream(cert)) {
+ CertificateFactory factory = CertificateFactory.getInstance("X509");
+ X509Certificate x509 = (X509Certificate) factory.generateCertificate(input);
+ MessageDigest md = MessageDigest.getInstance("SHA256");
+ byte[] publicKey = md.digest(x509.getEncoded());
+ return toHexFormat(publicKey);
+ }
+ }
+
+ private static String toHexFormat(byte[] bytes) {
+ StringBuilder builder = new StringBuilder(bytes.length * 2);
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(bytes[i]);
+ int length = hex.length();
+ if (length == 1) {
+ hex = "0" + hex;
+ }
+ if (length > 2) {
+ hex = hex.substring(length - 2, length);
+ }
+ builder.append(hex.toUpperCase());
+ if (i < (bytes.length - 1)) {
+ builder.append(':');
+ }
+ }
+ return builder.toString();
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java
new file mode 100644
index 0000000000000000000000000000000000000000..a86deea5a02b4b16825104c4596ea2bb4f372813
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.os.Bundle;
+import android.service.autofill.FillContext;
+import android.service.autofill.SaveInfo;
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.autofill.AutofillValue;
+
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+public final class Util {
+
+ public static final String EXTRA_DATASET_NAME = "dataset_name";
+ public static final String EXTRA_FOR_RESPONSE = "for_response";
+ public static final NodeFilter AUTOFILL_ID_FILTER = (node, id) ->
+ id.equals(node.getAutofillId());
+ private static final String TAG = "AutofillSample";
+ public static LogLevel sLoggingLevel = LogLevel.Off;
+
+ private static void bundleToString(StringBuilder builder, Bundle data) {
+ final Set