content: /*
   * 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 static com.example.android.autofill.app.Util.bundleToString;
  
  import android.content.Context;
  import android.graphics.Rect;
  import android.util.ArraySet;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.SparseArray;
  import android.view.View;
  import android.view.ViewStructure;
  import android.view.autofill.AutofillManager;
  import android.view.autofill.AutofillValue;
  
  import com.example.android.autofill.app.R;
  import com.example.android.autofill.app.Util;
  
  import java.text.DateFormat;
  import java.util.Date;
  
  /**
   * A custom View with a virtual structure that implements the Autofill APIs.
   */
  public class CustomVirtualView extends AbstractCustomVirtualView {
  
      private static final String TAG = "CustomView";
  
      protected final AutofillManager mAutofillManager;
      private final SparseArray<Partition> mPartitionsByAutofillId = new SparseArray<>();
  
      public CustomVirtualView(Context context) {
          this(context, null);
      }
  
      public CustomVirtualView(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public CustomVirtualView(Context context, AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
  
      public CustomVirtualView(Context context, AttributeSet attrs, int defStyleAttr,
              int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          mAutofillManager = context.getSystemService(AutofillManager.class);
      }
  
      @Override
      public void autofill(SparseArray<AutofillValue> 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<String> 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 notifyFocusGained(int virtualId, Rect bounds) {
          mAutofillManager.notifyViewEntered(this, virtualId, bounds);
      }
  
      @Override
      protected void notifyFocusLost(int virtualId) {
          mAutofillManager.notifyViewExited(this, virtualId);
      }
  
      @Override
      protected void onLineAdded(int id, Partition partition) {
          mPartitionsByAutofillId.put(id, partition);
      }
  }
  