1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.autofill;
18 
19 import static android.view.autofill.Helper.sDebug;
20 
21 import android.annotation.NonNull;
22 import android.annotation.TestApi;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.util.Log;
26 import android.view.autofill.AutofillId;
27 
28 import com.android.internal.util.Preconditions;
29 
30 import java.util.Arrays;
31 
32 /**
33  * Validator that returns {@code true} if the number created by concatenating all given fields
34  * pass a Luhn algorithm checksum. All non-digits are ignored.
35  *
36  * <p>See {@link SaveInfo.Builder#setValidator(Validator)} for examples.
37  */
38 public final class LuhnChecksumValidator extends InternalValidator implements Validator,
39         Parcelable {
40     private static final String TAG = "LuhnChecksumValidator";
41 
42     private final AutofillId[] mIds;
43 
44     /**
45       * Default constructor.
46       *
47       * @param ids id of fields that comprises the number to be checked.
48       */
LuhnChecksumValidator(@onNull AutofillId... ids)49     public LuhnChecksumValidator(@NonNull AutofillId... ids) {
50         mIds = Preconditions.checkArrayElementsNotNull(ids, "ids");
51     }
52 
53     /**
54      * Checks if the Luhn checksum is valid.
55      *
56      * @param number The number including the checksum
57      */
isLuhnChecksumValid(@onNull String number)58     private static boolean isLuhnChecksumValid(@NonNull String number) {
59         int sum = 0;
60         boolean isDoubled = false;
61 
62         for (int i = number.length() - 1; i >= 0; i--) {
63             final int digit = number.charAt(i) - '0';
64             if (digit < 0 || digit > 9) {
65                 // Ignore non-digits
66                 continue;
67             }
68 
69             int addend;
70             if (isDoubled) {
71                 addend = digit * 2;
72                 if (addend > 9) {
73                     addend -= 9;
74                 }
75             } else {
76                 addend = digit;
77             }
78             sum += addend;
79             isDoubled = !isDoubled;
80         }
81 
82         return sum % 10 == 0;
83     }
84 
85     /** @hide */
86     @Override
87     @TestApi
isValid(@onNull ValueFinder finder)88     public boolean isValid(@NonNull ValueFinder finder) {
89         if (mIds == null || mIds.length == 0) return false;
90 
91         final StringBuilder builder = new StringBuilder();
92         for (AutofillId id : mIds) {
93             final String partialNumber = finder.findByAutofillId(id);
94             if (partialNumber == null) {
95                 if (sDebug) Log.d(TAG, "No partial number for id " + id);
96                 return false;
97             }
98             builder.append(partialNumber);
99         }
100 
101         final String number = builder.toString();
102         boolean valid = isLuhnChecksumValid(number);
103         if (sDebug) Log.d(TAG, "isValid(" + number.length() + " chars): " + valid);
104         return valid;
105     }
106 
107     @Override
toString()108     public String toString() {
109         if (!sDebug) return super.toString();
110 
111         return "LuhnChecksumValidator: [ids=" + Arrays.toString(mIds) + "]";
112     }
113 
114     /////////////////////////////////////
115     // Parcelable "contract" methods. //
116     /////////////////////////////////////
117     @Override
describeContents()118     public int describeContents() {
119         return 0;
120     }
121 
122     @Override
writeToParcel(Parcel parcel, int flags)123     public void writeToParcel(Parcel parcel, int flags) {
124         parcel.writeParcelableArray(mIds, flags);
125     }
126 
127     public static final @android.annotation.NonNull Parcelable.Creator<LuhnChecksumValidator> CREATOR =
128             new Parcelable.Creator<LuhnChecksumValidator>() {
129         @Override
130         public LuhnChecksumValidator createFromParcel(Parcel parcel) {
131             return new LuhnChecksumValidator(parcel.readParcelableArray(null, AutofillId.class));
132         }
133 
134         @Override
135         public LuhnChecksumValidator[] newArray(int size) {
136             return new LuhnChecksumValidator[size];
137         }
138     };
139 }
140