diff options
Diffstat (limited to 'input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt')
-rw-r--r-- | input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt new file mode 100644 index 00000000..c5a7989e --- /dev/null +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt @@ -0,0 +1,273 @@ +/* + * 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 com.example.android.autofillframework.CommonUtil.bundleToString + + +/** + * Custom View with virtual child views for Username/Password text fields. + */ +class CustomVirtualView(context: Context, attrs: AttributeSet) : View(context, attrs) { + + private val mLines = ArrayList<Line>() + private val mItems = SparseArray<Item>() + private val mAfm: AutofillManager + + private var mFocusedLine: Line? = null + private val mTextPaint: Paint + private val mTextHeight: Int + private val mTopMargin: Int + private val mLeftMargin: Int + private val mVerticalGap: Int + private val mLineLength: Int + private val mFocusedColor: Int + private val mUnfocusedColor: Int + + private val mUsernameLine: Line + private val mPasswordLine: Line + + init { + + mAfm = context.getSystemService(AutofillManager::class.java) + + mTextPaint = Paint() + + mUnfocusedColor = Color.BLACK + mFocusedColor = Color.RED + mTextPaint.style = Style.FILL + mTopMargin = 100 + mLeftMargin = 100 + mTextHeight = 90 + mVerticalGap = 10 + + mLineLength = mTextHeight + mVerticalGap + mTextPaint.textSize = mTextHeight.toFloat() + mUsernameLine = addLine("usernameField", context.getString(R.string.username_label), + arrayOf(View.AUTOFILL_HINT_USERNAME), " ", true) + mPasswordLine = addLine("passwordField", context.getString(R.string.password_label), + arrayOf(View.AUTOFILL_HINT_PASSWORD), " ", false) + + Log.d(TAG, "Text height: " + mTextHeight) + } + + override fun autofill(values: SparseArray<AutofillValue>) { + // User has just selected a Dataset from the list of Autofill suggestions and the Dataset's + // AutofillValue gets passed into this method. + Log.d(TAG, "autoFill(): " + values) + for (i in 0..values.size() - 1) { + val id = values.keyAt(i) + val value = values.valueAt(i) + val item = mItems.get(id) + if (item == null) { + Log.w(TAG, "No item for id " + id) + return + } + if (!item.editable) { + Log.w(TAG, "Item for id $id is not editable: $item") + return + } + // Set the item's text to the text wrapped in the AutofillValue. + item.text = value.textValue + } + postInvalidate() + } + + override fun onProvideAutofillVirtualStructure(structure: ViewStructure, flags: Int) { + // Build a ViewStructure to pack in AutoFillService requests. + structure.setClassName(javaClass.name) + val childrenSize = mItems.size() + Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags + ", items = " + + childrenSize + ", extras: " + bundleToString(structure.extras)) + var index = structure.addChildCount(childrenSize) + for (i in 0..childrenSize - 1) { + val item = mItems.valueAt(i) + Log.d(TAG, "Adding new child at index $index: $item") + val child = structure.newChild(index) + child.setAutofillId(structure, item.id) + child.setAutofillHints(item.hints) + child.setAutofillType(item.type) + child.setDataIsSensitive(!item.sanitized) + child.text = item.text + child.setAutofillValue(AutofillValue.forText(item.text)) + child.setFocused(item.focused) + child.setId(item.id, context.packageName, null, item.line.idEntry) + child.setClassName(item.className) + index++ + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + Log.d(TAG, "onDraw: " + mLines.size + " lines; canvas:" + canvas) + var x: Float + var y = (mTopMargin + mLineLength).toFloat() + for (i in mLines.indices) { + x = mLeftMargin.toFloat() + val line = mLines[i] + Log.v(TAG, "Drawing '" + line + "' at " + x + "x" + y) + mTextPaint.color = if (line.fieldTextItem.focused) mFocusedColor else mUnfocusedColor + val readOnlyText = line.labelItem.text.toString() + ": [" + val writeText = line.fieldTextItem.text.toString() + "]" + // Paints the label first... + canvas.drawText(readOnlyText, x, y, mTextPaint) + // ...then paints the edit text and sets the proper boundary + val deltaX = mTextPaint.measureText(readOnlyText) + x += deltaX + line.bounds.set(x.toInt(), (y - mLineLength).toInt(), + (x + mTextPaint.measureText(writeText)).toInt(), y.toInt()) + Log.d(TAG, "setBounds(" + x + ", " + y + "): " + line.bounds) + canvas.drawText(writeText, x, y, mTextPaint) + y += mLineLength.toFloat() + } + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + val y = event.y.toInt() + Log.d(TAG, "Touched: y=$y, range=$mLineLength, top=$mTopMargin") + var lowerY = mTopMargin + var upperY = -1 + for (i in mLines.indices) { + upperY = lowerY + mLineLength + val line = mLines[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 += mLineLength + } + return super.onTouchEvent(event) + } + + val usernameText: CharSequence + get() = mUsernameLine.fieldTextItem.text + + val passwordText: CharSequence + get() = mPasswordLine.fieldTextItem.text + + fun resetFields() { + mUsernameLine.reset() + mPasswordLine.reset() + postInvalidate() + } + + private fun addLine(idEntry: String, label: String, hints: Array<String>, text: String, sanitized: Boolean): Line { + val line = Line(idEntry, label, hints, text, sanitized) + mLines.add(line) + mItems.put(line.labelItem.id, line.labelItem) + mItems.put(line.fieldTextItem.id, line.fieldTextItem) + return line + } + + private class Item internal constructor(val line: Line, val id: Int, val hints: Array<String>?, val type: Int, var text: CharSequence, val editable: Boolean, val sanitized: Boolean) { + var focused = false + + override fun toString(): String { + return id.toString() + ": " + text + if (editable) + " (editable)" + else + " (read-only)" + if (sanitized) " (sanitized)" else " (sensitive" + } + + val className: String + get() = if (editable) EditText::class.java.name else TextView::class.java.name + } + + private inner class Line constructor(val idEntry: String, label: String, hints: Array<String>, text: String, sanitized: Boolean) { + + // Boundaries of the text field, relative to the CustomView + internal val bounds = Rect() + var labelItem: Item + var fieldTextItem: Item + + init { + this.labelItem = Item(this, ++nextId, null, View.AUTOFILL_TYPE_NONE, label, false, true) + this.fieldTextItem = Item(this, ++nextId, hints, View.AUTOFILL_TYPE_TEXT, text, true, sanitized) + } + + internal fun changeFocus(focused: Boolean) { + fieldTextItem.focused = focused + if (focused) { + val absBounds = absCoordinates + Log.d(TAG, "focus gained on " + fieldTextItem.id + "; absBounds=" + absBounds) + mAfm.notifyViewEntered(this@CustomVirtualView, fieldTextItem.id, absBounds) + } else { + Log.d(TAG, "focus lost on " + fieldTextItem.id) + mAfm.notifyViewExited(this@CustomVirtualView, fieldTextItem.id) + } + } + + private // Must offset the boundaries so they're relative to the CustomView. + val absCoordinates: Rect + get() { + val offset = IntArray(2) + getLocationOnScreen(offset) + val absBounds = 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 + } + + fun reset() { + fieldTextItem.text = " " + } + + override fun toString(): String { + return "Label: " + labelItem + " Text: " + fieldTextItem + " Focused: " + + fieldTextItem.focused + } + } + + companion object { + + private val TAG = "CustomView" + + private var nextId: Int = 0 + } +}
\ No newline at end of file |