aboutsummaryrefslogtreecommitdiff
path: root/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice
diff options
context:
space:
mode:
authorDouglas Sigelbaum <sigelbaum@google.com>2017-06-07 16:21:09 -0400
committerDouglas Sigelbaum <sigelbaum@google.com>2017-06-07 17:53:35 -0400
commitd44566a42776d03195b15ba394d0d861306c0057 (patch)
tree8b24e014ce03b3e50826ef628bf283459d46c8f6 /input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice
parentb7d6772dc5b2a35a1d23cd274f9e3e17b4b55b08 (diff)
downloadandroid-d44566a42776d03195b15ba394d0d861306c0057.tar.gz
Renaming sample autofill service to Multidataset
AutofillService. Bug: 38182790 Test: manual Change-Id: I21784ece130355f9bd1efec811bba7c0bfba5b70
Diffstat (limited to 'input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice')
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.kt134
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.kt87
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.kt91
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.kt78
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.kt40
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.kt105
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/AutofillField.kt82
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/AutofillFieldsCollection.kt51
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/ClientFormData.kt103
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/SavableAutofillData.kt52
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.kt77
-rw-r--r--input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.kt147
12 files changed, 1047 insertions, 0 deletions
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.kt
new file mode 100644
index 00000000..d3e07ce2
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.util.Log
+import android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE
+import android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT
+import android.widget.Toast
+import com.example.android.autofillframework.CommonUtil.EXTRA_DATASET_NAME
+import com.example.android.autofillframework.CommonUtil.EXTRA_FOR_RESPONSE
+import com.example.android.autofillframework.CommonUtil.TAG
+import com.example.android.autofillframework.R
+import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsAutofillRepository
+import com.example.android.autofillframework.multidatasetservice.settings.MyPreferences
+import kotlinx.android.synthetic.main.multidataset_service_auth_activity.cancel
+import kotlinx.android.synthetic.main.multidataset_service_auth_activity.login
+import kotlinx.android.synthetic.main.multidataset_service_auth_activity.master_password
+
+/**
+ * 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.
+ */
+class AuthActivity : Activity() {
+
+ private var mReplyIntent: Intent? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.multidataset_service_auth_activity)
+ login.setOnClickListener { submitLogin() }
+ cancel.setOnClickListener {
+ onFailure()
+ this@AuthActivity.finish()
+ }
+ }
+
+ private fun submitLogin() {
+ val password = master_password.text
+ if (password.toString() == MyPreferences.getMasterPassword(this@AuthActivity)) {
+ onSuccess()
+ } else {
+ Toast.makeText(this, "Password incorrect", Toast.LENGTH_SHORT).show()
+ onFailure()
+ }
+ finish()
+ }
+
+ override fun finish() {
+ if (mReplyIntent != null) {
+ setResult(Activity.RESULT_OK, mReplyIntent)
+ } else {
+ setResult(Activity.RESULT_CANCELED)
+ }
+ super.finish()
+ }
+
+ private fun onFailure() {
+ Log.w(TAG, "Failed auth.")
+ mReplyIntent = null
+ }
+
+ private fun onSuccess() {
+ val intent = intent
+ val forResponse = intent.getBooleanExtra(EXTRA_FOR_RESPONSE, true)
+ val structure = intent.getParcelableExtra<AssistStructure>(EXTRA_ASSIST_STRUCTURE)
+ val parser = StructureParser(structure)
+ parser.parseForFill()
+ val autofillFields = parser.autofillFields
+ mReplyIntent = Intent()
+ val clientFormDataMap = SharedPrefsAutofillRepository
+ .getClientFormData(this, autofillFields.focusedAutofillHints, autofillFields.allAutofillHints)
+ if (forResponse) {
+ AutofillHelper.newResponse(this, false, autofillFields, clientFormDataMap)?.let(this::setResponseIntent)
+ } else {
+ val datasetName = intent.getStringExtra(EXTRA_DATASET_NAME)
+ clientFormDataMap?.let {
+ it[datasetName]?.let { clientFormData ->
+ AutofillHelper.newDataset(this, autofillFields, clientFormData, false)?.let(this::setDatasetIntent)
+ }
+ }
+ }
+ }
+
+ private fun setResponseIntent(fillResponse: FillResponse) {
+ mReplyIntent?.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse)
+ }
+
+ private fun setDatasetIntent(dataset: Dataset) {
+ mReplyIntent?.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset)
+ }
+
+ companion object {
+
+ // Unique autofillId for dataset intents.
+ private var datasetPendingIntentId = 0
+
+ internal fun getAuthIntentSenderForResponse(context: Context): IntentSender {
+ val intent = Intent(context, AuthActivity::class.java)
+ return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
+ .intentSender
+ }
+
+ internal fun getAuthIntentSenderForDataset(context: Context, datasetName: String): IntentSender {
+ val intent = Intent(context, AuthActivity::class.java)
+ intent.putExtra(EXTRA_DATASET_NAME, datasetName)
+ intent.putExtra(EXTRA_FOR_RESPONSE, false)
+ return PendingIntent.getActivity(context, ++datasetPendingIntentId, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT).intentSender
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.kt
new file mode 100644
index 00000000..4d67d5bc
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.kt
@@ -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.autofillframework.multidatasetservice
+
+import android.content.Context
+import android.service.autofill.Dataset
+import android.service.autofill.FillResponse
+import android.service.autofill.SaveInfo
+import android.util.Log
+import android.widget.RemoteViews
+import com.example.android.autofillframework.CommonUtil.TAG
+import com.example.android.autofillframework.R
+import com.example.android.autofillframework.multidatasetservice.model.AutofillFieldsCollection
+import com.example.android.autofillframework.multidatasetservice.model.ClientFormData
+import java.util.HashMap
+
+/**
+ * This is a class containing helper methods for building Autofill Datasets and Responses.
+ */
+object AutofillHelper {
+
+ /**
+ * Wraps autofill data in a [Dataset] object which can then be sent back to the
+ * client View.
+ */
+ fun newDataset(context: Context, autofillFields: AutofillFieldsCollection,
+ clientFormData: ClientFormData, datasetAuth: Boolean): Dataset? {
+ clientFormData.datasetName?.let { datasetName ->
+ val datasetBuilder = Dataset.Builder(newRemoteViews(context.packageName, datasetName))
+ val setValueAtLeastOnce = clientFormData.applyToFields(autofillFields, datasetBuilder)
+ if (datasetAuth) {
+ val sender = AuthActivity.getAuthIntentSenderForDataset(context, datasetName)
+ datasetBuilder.setAuthentication(sender)
+ }
+ if (setValueAtLeastOnce) {
+ return datasetBuilder.build()
+ }
+ }
+ return null
+ }
+
+ fun newRemoteViews(packageName: String, remoteViewsText: String): RemoteViews {
+ val presentation = RemoteViews(packageName, R.layout.multidataset_service_list_item)
+ presentation.setTextViewText(R.id.text1, remoteViewsText)
+ return presentation
+ }
+
+ /**
+ * Wraps autofill data in a [FillResponse] object (essentially a series of Datasets) which can
+ * then be sent back to the client View.
+ */
+ fun newResponse(context: Context,
+ datasetAuth: Boolean, autofillFields: AutofillFieldsCollection,
+ clientFormDataMap: HashMap<String, ClientFormData>?): FillResponse? {
+ val responseBuilder = FillResponse.Builder()
+ clientFormDataMap?.keys?.let { datasetNames ->
+ for (datasetName in datasetNames) {
+ clientFormDataMap[datasetName]?.let { clientFormData ->
+ val dataset = newDataset(context, autofillFields, clientFormData, datasetAuth)
+ dataset?.let(responseBuilder::addDataset)
+ }
+ }
+ }
+ if (autofillFields.saveType != 0) {
+ val autofillIds = autofillFields.autofillIds
+ responseBuilder.setSaveInfo(SaveInfo.Builder(autofillFields.saveType,
+ autofillIds.toTypedArray()).build())
+ return responseBuilder.build()
+ } else {
+ Log.d(TAG, "These fields are not meant to be saved by autofill.")
+ return null
+ }
+ }
+} \ No newline at end of file
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.kt
new file mode 100644
index 00000000..f9169029
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.kt
@@ -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.autofillframework.multidatasetservice
+
+import android.os.CancellationSignal
+import android.service.autofill.AutofillService
+import android.service.autofill.FillCallback
+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 com.example.android.autofillframework.CommonUtil.TAG
+import com.example.android.autofillframework.CommonUtil.bundleToString
+import com.example.android.autofillframework.R
+import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsAutofillRepository
+import com.example.android.autofillframework.multidatasetservice.settings.MyPreferences
+
+class MyAutofillService : AutofillService() {
+
+ override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal,
+ callback: FillCallback) {
+ val structure = request.getFillContexts().get(request.getFillContexts().size - 1).structure
+ val data = request.clientState
+ 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.activityComponent.toShortString()
+ .contains("com.example.android.autofillframework.service")) {
+ callback.onSuccess(null)
+ return
+ }
+ cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill not implemented in this sample.") }
+ // Parse AutoFill data in Activity
+ val parser = StructureParser(structure)
+ parser.parseForFill()
+ val autofillFields = parser.autofillFields
+
+ val responseBuilder = FillResponse.Builder()
+ // Check user's settings for authenticating Responses and Datasets.
+ val responseAuth = MyPreferences.isResponseAuth(this)
+ if (responseAuth) {
+ // If the entire Autofill Response is authenticated, AuthActivity is used
+ // to generate Response.
+ val sender = AuthActivity.getAuthIntentSenderForResponse(this)
+ val presentation = AutofillHelper
+ .newRemoteViews(packageName, getString(R.string.autofill_sign_in_prompt))
+ responseBuilder
+ .setAuthentication(autofillFields.autofillIds.toTypedArray(), sender, presentation)
+ callback.onSuccess(responseBuilder.build())
+ } else {
+ val datasetAuth = MyPreferences.isDatasetAuth(this)
+ val clientFormDataMap = SharedPrefsAutofillRepository.getClientFormData(this,
+ autofillFields.focusedAutofillHints, autofillFields.allAutofillHints)
+ val response = AutofillHelper.newResponse(this, datasetAuth, autofillFields, clientFormDataMap)
+ callback.onSuccess(response)
+ }
+ }
+
+ override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
+ val context = request.fillContexts
+ val structure = context[context.size - 1].structure
+ val data = request.clientState
+ Log.d(TAG, "onSaveRequest(): data=" + bundleToString(data))
+ val parser = StructureParser(structure)
+ parser.parseForSave()
+ SharedPrefsAutofillRepository.saveClientFormData(this, parser.clientFormData)
+ }
+
+ override fun onConnected() {
+ Log.d(TAG, "onConnected")
+ }
+
+ override fun onDisconnected() {
+ Log.d(TAG, "onDisconnected")
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.kt
new file mode 100644
index 00000000..78cab5ee
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.kt
@@ -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.autofillframework.multidatasetservice
+
+import android.app.assist.AssistStructure
+import android.app.assist.AssistStructure.ViewNode
+import android.util.Log
+import com.example.android.autofillframework.CommonUtil.TAG
+import com.example.android.autofillframework.multidatasetservice.model.AutofillField
+import com.example.android.autofillframework.multidatasetservice.model.AutofillFieldsCollection
+import com.example.android.autofillframework.multidatasetservice.model.ClientFormData
+import com.example.android.autofillframework.multidatasetservice.model.SavableAutofillData
+
+/**
+ * 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.
+ */
+internal class StructureParser(private val mStructure: AssistStructure) {
+ val autofillFields = AutofillFieldsCollection()
+ var clientFormData: ClientFormData = ClientFormData()
+ private set
+
+
+ fun parseForFill() {
+ parse(true)
+ }
+
+ fun parseForSave() {
+ parse(false)
+ }
+
+ /**
+ * Traverse AssistStructure and add ViewNode metadata to a flat list.
+ */
+ private fun parse(forFill: Boolean) {
+ Log.d(TAG, "Parsing structure for " + mStructure.activityComponent)
+ val nodes = mStructure.windowNodeCount
+ clientFormData = ClientFormData()
+ for (i in 0..nodes - 1) {
+ val node = mStructure.getWindowNodeAt(i)
+ val view = node.rootViewNode
+ parseLocked(forFill, view)
+ }
+ }
+
+ private fun parseLocked(forFill: Boolean, viewNode: ViewNode) {
+ viewNode.autofillHints?.let { autofillHints ->
+ if (autofillHints.isNotEmpty()) {
+ if (forFill) {
+ autofillFields.add(AutofillField(viewNode))
+ } else {
+ clientFormData.setAutofillValuesForHints(viewNode.autofillHints,
+ SavableAutofillData(viewNode))
+ }
+ }
+ }
+ val childrenSize = viewNode.childCount
+ if (childrenSize > 0) {
+ for (i in 0..childrenSize - 1) {
+ parseLocked(forFill, viewNode.getChildAt(i))
+ }
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.kt
new file mode 100644
index 00000000..9e4d6e20
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.kt
@@ -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.autofillframework.multidatasetservice.datasource
+
+import android.content.Context
+import com.example.android.autofillframework.multidatasetservice.model.ClientFormData
+import java.util.HashMap
+
+interface AutofillRepository {
+
+ /**
+ * Gets saved ClientFormData that contains some objects that can autofill fields with these
+ * `autofillHints`.
+ */
+ fun getClientFormData(context: Context, focusedAutofillHints: List<String>,
+ allAutofillHints: List<String>): HashMap<String, ClientFormData>?
+
+ /**
+ * Saves LoginCredential under this datasetName.
+ */
+ fun saveClientFormData(context: Context, clientFormData: ClientFormData)
+
+ /**
+ * Clears all data.
+ */
+ fun clear(context: Context)
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.kt
new file mode 100644
index 00000000..8aeac0df
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.kt
@@ -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.autofillframework.multidatasetservice.datasource
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.util.ArraySet
+import com.example.android.autofillframework.multidatasetservice.model.ClientFormData
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+
+
+/**
+ * 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.
+ */
+object SharedPrefsAutofillRepository : AutofillRepository {
+ private val SHARED_PREF_KEY = "com.example.android.autofillframework.service"
+ private val CLIENT_FORM_DATA_KEY = "loginCredentialDatasets"
+ private val DATASET_NUMBER_KEY = "datasetNumber"
+
+ private fun getPrefs(context: Context): SharedPreferences {
+ return context.applicationContext.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE)
+ }
+
+ override fun getClientFormData(context: Context, focusedAutofillHints: List<String>,
+ allAutofillHints: List<String>): HashMap<String, ClientFormData>? {
+ var hasDataForFocusedAutofillHints = false
+ val clientFormDataMap = HashMap<String, ClientFormData>()
+ val clientFormDataStringSet = getAllAutofillDataStringSet(context)
+ for (clientFormDataString in clientFormDataStringSet) {
+ val type = object : TypeToken<ClientFormData>() {}.type
+ Gson().fromJson<ClientFormData>(clientFormDataString, type)?.let { clientFormData ->
+ if (clientFormData.helpsWithHints(focusedAutofillHints)) {
+ // Saved data has data relevant to at least 1 of the hints associated with the
+ // View in focus.
+ hasDataForFocusedAutofillHints = true
+ clientFormData.datasetName?.let { datasetName ->
+ if (clientFormData.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(datasetName, clientFormData)
+ }
+ }
+ }
+ }
+ }
+ if (hasDataForFocusedAutofillHints) {
+ return clientFormDataMap
+ } else {
+ return null
+ }
+ }
+
+ override fun saveClientFormData(context: Context, clientFormData: ClientFormData) {
+ val datasetName = "dataset-" + getDatasetNumber(context)
+ clientFormData.datasetName = datasetName
+ val allAutofillData = getAllAutofillDataStringSet(context)
+ allAutofillData.add(Gson().toJson(clientFormData).toString())
+ saveAllAutofillDataStringSet(context, allAutofillData)
+ incrementDatasetNumber(context)
+ }
+
+ override fun clear(context: Context) {
+ getPrefs(context).edit().remove(CLIENT_FORM_DATA_KEY).remove(DATASET_NUMBER_KEY).apply()
+ }
+
+ private fun getAllAutofillDataStringSet(context: Context): MutableSet<String> {
+ return getPrefs(context).getStringSet(CLIENT_FORM_DATA_KEY, ArraySet<String>())
+ }
+
+ private fun saveAllAutofillDataStringSet(context: Context, allAutofillDataStringSet: Set<String>) {
+ getPrefs(context).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 fun getDatasetNumber(context: Context): Int {
+ return getPrefs(context).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 fun incrementDatasetNumber(context: Context) {
+ getPrefs(context).edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber(context) + 1).apply()
+ }
+} \ No newline at end of file
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/AutofillField.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/AutofillField.kt
new file mode 100644
index 00000000..474454a3
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/AutofillField.kt
@@ -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.autofillframework.multidatasetservice.model
+
+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 [ViewNode] that contains only autofill-relevant metadata. It also
+ * contains a `saveType` flag that is calculated based on the [ViewNode]'s autofill hints.
+ */
+class AutofillField(view: ViewNode) {
+ var saveType = 0
+ private set
+
+ val autofillHints: Array<String> = view.autofillHints
+ val autofillId: AutofillId = view.autofillId
+ val autofillType: Int = view.autofillType
+ val autofillOptions: Array<CharSequence>? = view.autofillOptions
+ val isFocused: Boolean = view.isFocused
+
+ init {
+ updateSaveTypeFromHints()
+ }
+
+ /**
+ * When the [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.
+ */
+ fun getAutofillOptionIndex(value: CharSequence): Int? {
+ return autofillOptions?.indexOf(value)
+ }
+
+ private fun updateSaveTypeFromHints() {
+ saveType = 0
+ for (hint in autofillHints) {
+ when (hint) {
+ View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE,
+ View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY,
+ View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH,
+ View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR,
+ View.AUTOFILL_HINT_CREDIT_CARD_NUMBER,
+ View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE -> {
+ saveType = saveType or SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD
+ }
+ View.AUTOFILL_HINT_EMAIL_ADDRESS -> {
+ saveType = saveType or SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS
+ }
+ View.AUTOFILL_HINT_PHONE, View.AUTOFILL_HINT_NAME -> {
+ saveType = saveType or SaveInfo.SAVE_DATA_TYPE_GENERIC
+ }
+ View.AUTOFILL_HINT_PASSWORD -> {
+ saveType = saveType or SaveInfo.SAVE_DATA_TYPE_PASSWORD
+ saveType = saveType and SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS.inv()
+ saveType = saveType and SaveInfo.SAVE_DATA_TYPE_USERNAME.inv()
+ }
+ View.AUTOFILL_HINT_POSTAL_ADDRESS,
+ View.AUTOFILL_HINT_POSTAL_CODE -> {
+ saveType = saveType or SaveInfo.SAVE_DATA_TYPE_ADDRESS
+ }
+ View.AUTOFILL_HINT_USERNAME -> {
+ saveType = saveType or SaveInfo.SAVE_DATA_TYPE_USERNAME
+ }
+ }
+ }
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/AutofillFieldsCollection.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/AutofillFieldsCollection.kt
new file mode 100644
index 00000000..c62ea327
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/AutofillFieldsCollection.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.view.autofill.AutofillId
+import java.util.ArrayList
+import java.util.HashMap
+
+/**
+ * Data structure that stores a collection of `AutofillField`s. Contains all of the client's `View`
+ * hierarchy autofill-relevant metadata.
+ */
+data class AutofillFieldsCollection(val autofillIds: ArrayList<AutofillId> = ArrayList<AutofillId>(),
+ val allAutofillHints: ArrayList<String> = ArrayList<String>(),
+ val focusedAutofillHints: ArrayList<String> = ArrayList<String>()) {
+
+ private val autofillHintsToFieldsMap = HashMap<String, MutableList<AutofillField>>()
+ var saveType = 0
+ private set
+
+ fun add(autofillField: AutofillField) {
+ saveType = saveType or autofillField.saveType
+ autofillIds.add(autofillField.autofillId)
+ val hintsList = autofillField.autofillHints
+ allAutofillHints.addAll(hintsList)
+ if (autofillField.isFocused) {
+ focusedAutofillHints.addAll(hintsList)
+ }
+ autofillField.autofillHints.forEach { autofillHint ->
+ autofillHintsToFieldsMap[autofillHint] = autofillHintsToFieldsMap[autofillHint] ?: ArrayList<AutofillField>()
+ autofillHintsToFieldsMap[autofillHint]?.add(autofillField)
+ }
+ }
+
+ fun getFieldsForHint(hint: String): MutableList<AutofillField>? {
+ return autofillHintsToFieldsMap[hint]
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/ClientFormData.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/ClientFormData.kt
new file mode 100644
index 00000000..8f434abe
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/ClientFormData.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.util.Log
+import android.view.View
+import android.view.autofill.AutofillId
+import android.view.autofill.AutofillValue
+import java.util.HashMap
+
+
+/**
+ * ClientFormData is the model that represents all of the form data on a client app's page, plus the
+ * dataset name associated with it.
+ */
+class ClientFormData constructor(var datasetName: String? = null,
+ private val hintMap: HashMap<String, SavableAutofillData> = HashMap<String, SavableAutofillData>()) {
+
+ private val TAG = "ClientFormData"
+
+ /**
+ * Sets values for a list of autofillHints.
+ */
+ fun setAutofillValuesForHints(autofillHints: Array<String>, autofillData: SavableAutofillData) {
+ autofillHints.forEach { hint ->
+ hintMap[hint] = autofillData
+ }
+ }
+
+ /**
+ * Populates a [Dataset.Builder] with appropriate values for each [AutofillId]
+ * in a `AutofillFieldsCollection`.
+ */
+ fun applyToFields(autofillFieldsCollection: AutofillFieldsCollection,
+ datasetBuilder: Dataset.Builder): Boolean {
+ var setValueAtLeastOnce = false
+ for (hint in autofillFieldsCollection.allAutofillHints) {
+ val autofillFields = autofillFieldsCollection.getFieldsForHint(hint) ?: continue
+ for (autofillField in autofillFields) {
+ val autofillId = autofillField.autofillId
+ val autofillType = autofillField.autofillType
+ val savedAutofillValue = hintMap[hint]
+ when (autofillType) {
+ View.AUTOFILL_TYPE_LIST -> {
+ savedAutofillValue?.textValue?.let(autofillField::getAutofillOptionIndex)?.let { index ->
+ datasetBuilder.setValue(autofillId, AutofillValue.forList(index))
+ setValueAtLeastOnce = true
+ }
+ }
+ View.AUTOFILL_TYPE_DATE -> {
+ savedAutofillValue?.dateValue?.let { date ->
+ datasetBuilder.setValue(autofillId, AutofillValue.forDate(date))
+ setValueAtLeastOnce = true
+ }
+ }
+ View.AUTOFILL_TYPE_TEXT -> {
+ savedAutofillValue?.textValue?.let { text ->
+ datasetBuilder.setValue(autofillId, AutofillValue.forText(text))
+ setValueAtLeastOnce = true
+ }
+ }
+ View.AUTOFILL_TYPE_TOGGLE -> {
+ savedAutofillValue?.toggleValue?.let { toggle ->
+ datasetBuilder.setValue(autofillId, AutofillValue.forToggle(toggle))
+ setValueAtLeastOnce = true
+ }
+ }
+ else -> Log.w(TAG, "Invalid autofill type - " + autofillType)
+ }
+ }
+ }
+ return setValueAtLeastOnce
+ }
+
+ /**
+ * Returns whether this model contains autofill data that is relevant to any of the
+ * autofillHints that are passed in.
+ */
+ fun helpsWithHints(autofillHints: List<String>): Boolean {
+ for (autofillHint in autofillHints) {
+ hintMap[autofillHint]?.let { savedAutofillValue ->
+ if (!savedAutofillValue.isNull()) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/SavableAutofillData.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/SavableAutofillData.kt
new file mode 100644
index 00000000..3c50d339
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/SavableAutofillData.kt
@@ -0,0 +1,52 @@
+/*
+ * 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 [AutofillValue].
+ */
+class SavableAutofillData(viewNode: AssistStructure.ViewNode) {
+ var textValue: CharSequence? = null
+ var dateValue: Long? = null
+ var toggleValue: Boolean? = null
+
+ init {
+ viewNode.autofillValue?.let { autofillValue ->
+ if (autofillValue.isList) {
+ val index = autofillValue.listValue
+ viewNode.autofillOptions?.let { autofillOptions ->
+ if (autofillOptions.size > index) {
+ textValue = autofillOptions[index]
+ }
+ }
+ } else if (autofillValue.isDate) {
+ dateValue = autofillValue.dateValue
+ } else if (autofillValue.isText) {
+ // Using toString of AutofillValue.getTextValue in order to save it to
+ // SharedPreferences.
+ textValue = autofillValue.textValue.toString()
+ } else {
+ }
+ }
+ }
+
+ fun isNull(): Boolean {
+ return textValue == null && dateValue == null && toggleValue == null
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.kt
new file mode 100644
index 00000000..1ddf3dab
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.Context
+import android.content.SharedPreferences
+import android.service.autofill.Dataset
+import android.service.autofill.FillResponse
+
+object MyPreferences {
+ private val TAG = "MyPreferences"
+
+ private val SHARED_PREF_KEY = "com.example.android.autofillframework.service.settings.MyPreferences"
+ private val RESPONSE_AUTH_KEY = "response_auth"
+ private val DATASET_AUTH_KEY = "dataset_auth"
+ private val MASTER_PASSWORD_KEY = "master_password"
+
+ private fun getPrefs(context: Context): SharedPreferences {
+ return context.applicationContext.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE)
+ }
+
+ /**
+ * Determines whether [FillResponse]s should require authentication.
+ */
+ fun isResponseAuth(context: Context): Boolean {
+ return getPrefs(context).getBoolean(RESPONSE_AUTH_KEY, false)
+ }
+
+ fun setResponseAuth(context: Context, responseAuth: Boolean) {
+ getPrefs(context).edit().putBoolean(RESPONSE_AUTH_KEY, responseAuth).apply()
+ }
+
+ /**
+ * Determines whether [Dataset]s should require authentication.
+ */
+ fun isDatasetAuth(context: Context): Boolean {
+ return getPrefs(context).getBoolean(DATASET_AUTH_KEY, false)
+ }
+
+ fun setDatasetAuth(context: Context, datasetAuth: Boolean) {
+ getPrefs(context).edit().putBoolean(DATASET_AUTH_KEY, datasetAuth).apply()
+ }
+
+ /**
+ * Gets autofill master password.
+ */
+ fun getMasterPassword(context: Context): String? {
+ return getPrefs(context).getString(MASTER_PASSWORD_KEY, null)
+ }
+
+ /**
+ * Sets autofill master password.
+ */
+ fun setMasterPassword(context: Context, masterPassword: String) {
+ getPrefs(context).edit().putString(MASTER_PASSWORD_KEY, masterPassword).apply()
+ }
+
+ /**
+ * Removes master password.
+ */
+ fun clearCredentials(context: Context) {
+ getPrefs(context).edit().remove(MASTER_PASSWORD_KEY).apply()
+ }
+}
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.kt
new file mode 100644
index 00000000..e314ccd7
--- /dev/null
+++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.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
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_credentials_container
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_credentials_icon
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_credentials_label
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_datasets_container
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_datasets_label
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_datasets_switch
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_responses_container
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_responses_label
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_auth_responses_switch
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_clear_data_container
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_clear_data_icon
+import kotlinx.android.synthetic.main.multidataset_service_settings_activity.settings_clear_data_label
+
+class SettingsActivity : AppCompatActivity() {
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(R.layout.multidataset_service_settings_activity)
+ setupSettingsSwitch(settings_auth_responses_container,
+ settings_auth_responses_label,
+ settings_auth_responses_switch,
+ MyPreferences.isResponseAuth(this),
+ CompoundButton.OnCheckedChangeListener { compoundButton, b ->
+ MyPreferences.setResponseAuth(this@SettingsActivity, b)
+ })
+ setupSettingsSwitch(settings_auth_datasets_container,
+ settings_auth_datasets_label,
+ settings_auth_datasets_switch,
+ MyPreferences.isDatasetAuth(this),
+ CompoundButton.OnCheckedChangeListener { compoundButton, b ->
+ MyPreferences.setDatasetAuth(this@SettingsActivity, b)
+ })
+ setupSettingsButton(settings_clear_data_container,
+ settings_clear_data_label,
+ settings_clear_data_icon,
+ View.OnClickListener { buildClearDataDialog().show() })
+
+ setupSettingsButton(settings_auth_credentials_container,
+ settings_auth_credentials_label,
+ settings_auth_credentials_icon,
+ View.OnClickListener {
+ MyPreferences.getMasterPassword(this@SettingsActivity)?.let {
+ buildCurrentCredentialsDialog().show()
+ } ?: buildNewCredentialsDialog().show()
+ })
+ }
+
+ private fun buildClearDataDialog(): AlertDialog {
+ return AlertDialog.Builder(this@SettingsActivity)
+ .setMessage(R.string.settings_clear_data_confirmation)
+ .setTitle(R.string.settings_clear_data_confirmation_title)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.ok) { dialog, which ->
+ SharedPrefsAutofillRepository.clear(this@SettingsActivity)
+ MyPreferences.clearCredentials(this@SettingsActivity)
+ dialog.dismiss()
+ }
+ .create()
+ }
+
+ private fun prepareCredentialsDialog(): AlertDialog.Builder {
+ return AlertDialog.Builder(this@SettingsActivity)
+ .setTitle(R.string.settings_auth_change_credentials_title)
+ .setNegativeButton(R.string.cancel, null)
+ }
+
+ private fun buildCurrentCredentialsDialog(): AlertDialog {
+ val currentPasswordField = LayoutInflater
+ .from(this@SettingsActivity)
+ .inflate(R.layout.multidataset_service_settings_authentication_dialog, null)
+ .findViewById<EditText>(R.id.master_password_field)
+ return prepareCredentialsDialog()
+ .setMessage(R.string.settings_auth_enter_current_password)
+ .setView(currentPasswordField)
+ .setPositiveButton(R.string.ok) { dialog, which ->
+ val password = currentPasswordField.text.toString()
+ if (MyPreferences.getMasterPassword(this@SettingsActivity) == password) {
+ buildNewCredentialsDialog().show()
+ dialog.dismiss()
+ }
+ }
+ .create()
+ }
+
+ private fun buildNewCredentialsDialog(): AlertDialog {
+ val newPasswordField = LayoutInflater
+ .from(this@SettingsActivity)
+ .inflate(R.layout.multidataset_service_settings_authentication_dialog, null)
+ .findViewById<EditText>(R.id.master_password_field)
+ return prepareCredentialsDialog()
+ .setMessage(R.string.settings_auth_enter_new_password)
+ .setView(newPasswordField)
+ .setPositiveButton(R.string.ok) { dialog, which ->
+ val password = newPasswordField.text.toString()
+ MyPreferences.setMasterPassword(this@SettingsActivity, password)
+ dialog.dismiss()
+ }
+ .create()
+ }
+
+ private fun setupSettingsSwitch(container: ViewGroup, switchLabelView: TextView, switchView: Switch, checked: Boolean,
+ checkedChangeListener: CompoundButton.OnCheckedChangeListener) {
+ val switchLabel = switchLabelView.text.toString()
+ switchView.contentDescription = switchLabel
+ switchView.isChecked = checked
+ container.setOnClickListener { switchView.performClick() }
+ switchView.setOnCheckedChangeListener(checkedChangeListener)
+ }
+
+ private fun setupSettingsButton(container: ViewGroup, buttonLabelView: TextView, imageView: ImageView,
+ onClickListener: View.OnClickListener) {
+ val buttonLabel = buttonLabelView.text.toString()
+ imageView.contentDescription = buttonLabel
+ container.setOnClickListener(onClickListener)
+ }
+}