content: /*
   * 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 android.service.autofill;
  
  import static android.view.autofill.Helper.sDebug;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.TestApi;
  import android.os.Bundle;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.util.ArrayMap;
  
  import com.android.internal.util.Preconditions;
  
  import java.util.ArrayList;
  import java.util.Collections;
  
  /**
   * Holds both a generic and package-specific userData used for
   * <a href="AutofillService.html#FieldClassification">field classification</a>.
   *
   * @hide
   */
  @TestApi
  public final class CompositeUserData implements FieldClassificationUserData, Parcelable {
  
      private final UserData mGenericUserData;
      private final UserData mPackageUserData;
  
      private final String[] mCategories;
      private final String[] mValues;
  
      public CompositeUserData(@Nullable UserData genericUserData,
              @NonNull UserData packageUserData) {
          mGenericUserData = genericUserData;
          mPackageUserData = packageUserData;
  
          final String[] packageCategoryIds = mPackageUserData.getCategoryIds();
          final String[] packageValues = mPackageUserData.getValues();
  
          final ArrayList<String> categoryIds = new ArrayList<>(packageCategoryIds.length);
          final ArrayList<String> values = new ArrayList<>(packageValues.length);
  
          Collections.addAll(categoryIds, packageCategoryIds);
          Collections.addAll(values, packageValues);
  
          if (mGenericUserData != null) {
              final String[] genericCategoryIds = mGenericUserData.getCategoryIds();
              final String[] genericValues = mGenericUserData.getValues();
              final int size = mGenericUserData.getCategoryIds().length;
              for (int i = 0; i < size; i++) {
                  if (!categoryIds.contains(genericCategoryIds[i])) {
                      categoryIds.add(genericCategoryIds[i]);
                      values.add(genericValues[i]);
                  }
              }
          }
  
          mCategories = new String[categoryIds.size()];
          categoryIds.toArray(mCategories);
          mValues = new String[values.size()];
          values.toArray(mValues);
      }
  
      @Nullable
      @Override
      public String getFieldClassificationAlgorithm() {
          final String packageDefaultAlgo = mPackageUserData.getFieldClassificationAlgorithm();
          if (packageDefaultAlgo != null) {
              return packageDefaultAlgo;
          } else {
              return mGenericUserData == null ? null :
                      mGenericUserData.getFieldClassificationAlgorithm();
          }
      }
  
      @Override
      public Bundle getDefaultFieldClassificationArgs() {
          final Bundle packageDefaultArgs = mPackageUserData.getDefaultFieldClassificationArgs();
          if (packageDefaultArgs != null) {
              return packageDefaultArgs;
          } else {
              return mGenericUserData == null ? null :
                      mGenericUserData.getDefaultFieldClassificationArgs();
          }
      }
  
      @Nullable
      @Override
      public String getFieldClassificationAlgorithmForCategory(@NonNull String categoryId) {
          Preconditions.checkNotNull(categoryId);
          final ArrayMap<String, String> categoryAlgorithms = getFieldClassificationAlgorithms();
          if (categoryAlgorithms == null || !categoryAlgorithms.containsKey(categoryId)) {
              return null;
          }
          return categoryAlgorithms.get(categoryId);
      }
  
      @Override
      public ArrayMap<String, String> getFieldClassificationAlgorithms() {
          final ArrayMap<String, String> packageAlgos = mPackageUserData
                  .getFieldClassificationAlgorithms();
          final ArrayMap<String, String> genericAlgos = mGenericUserData == null ? null :
                  mGenericUserData.getFieldClassificationAlgorithms();
  
          ArrayMap<String, String> categoryAlgorithms = null;
          if (packageAlgos != null || genericAlgos != null) {
              categoryAlgorithms = new ArrayMap<>();
              if (genericAlgos != null) {
                  categoryAlgorithms.putAll(genericAlgos);
              }
              if (packageAlgos != null) {
                  categoryAlgorithms.putAll(packageAlgos);
              }
          }
  
          return categoryAlgorithms;
      }
  
      @Override
      public ArrayMap<String, Bundle> getFieldClassificationArgs() {
          final ArrayMap<String, Bundle> packageArgs = mPackageUserData.getFieldClassificationArgs();
          final ArrayMap<String, Bundle> genericArgs = mGenericUserData == null ? null :
                  mGenericUserData.getFieldClassificationArgs();
  
          ArrayMap<String, Bundle> categoryArgs = null;
          if (packageArgs != null || genericArgs != null) {
              categoryArgs = new ArrayMap<>();
              if (genericArgs != null) {
                  categoryArgs.putAll(genericArgs);
              }
              if (packageArgs != null) {
                  categoryArgs.putAll(packageArgs);
              }
          }
  
          return categoryArgs;
      }
  
      @Override
      public String[] getCategoryIds() {
          return mCategories;
      }
  
      @Override
      public String[] getValues() {
          return mValues;
      }
  
      /////////////////////////////////////
      // Object "contract" methods. //
      /////////////////////////////////////
      @Override
      public String toString() {
          if (!sDebug) return super.toString();
  
          // OK to print UserData because UserData.toString() is PII-aware
          final StringBuilder builder = new StringBuilder("genericUserData=")
                  .append(mGenericUserData)
                  .append(", packageUserData=").append(mPackageUserData);
          return builder.toString();
      }
  
      /////////////////////////////////////
      // Parcelable "contract" methods. //
      /////////////////////////////////////
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel parcel, int flags) {
          parcel.writeParcelable(mGenericUserData, 0);
          parcel.writeParcelable(mPackageUserData, 0);
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<CompositeUserData> CREATOR =
              new Parcelable.Creator<CompositeUserData>() {
                  @Override
                  public CompositeUserData createFromParcel(Parcel parcel) {
                      // Always go through the builder to ensure the data ingested by
                      // the system obeys the contract of the builder to avoid attacks
                      // using specially crafted parcels.
                      final UserData genericUserData = parcel.readParcelable(null);
                      final UserData packageUserData = parcel.readParcelable(null);
                      return new CompositeUserData(genericUserData, packageUserData);
                  }
  
                  @Override
                  public CompositeUserData[] newArray(int size) {
                      return new CompositeUserData[size];
                  }
              };
  }
  