1 /*
2  * Copyright (C) 2009 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.accounts;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.Context;
23 import android.os.Build;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.os.RemoteException;
27 import android.os.ServiceManager;
28 import android.text.TextUtils;
29 import android.util.ArraySet;
30 import android.util.Log;
31 
32 import com.android.internal.annotations.GuardedBy;
33 
34 import java.util.Objects;
35 import java.util.Set;
36 
37 /**
38  * Value type that represents an Account in the {@link AccountManager}. This object is
39  * {@link Parcelable} and also overrides {@link #equals} and {@link #hashCode}, making it
40  * suitable for use as the key of a {@link java.util.Map}
41  */
42 public class Account implements Parcelable {
43     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
44     private static final String TAG = "Account";
45 
46     @GuardedBy("sAccessedAccounts")
47     private static final Set<Account> sAccessedAccounts = new ArraySet<>();
48 
49     public final String name;
50     public final String type;
51     private String mSafeName;
52     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
53     private final @Nullable String accessId;
54 
equals(@ullable Object o)55     public boolean equals(@Nullable Object o) {
56         if (o == this) return true;
57         if (!(o instanceof Account)) return false;
58         final Account other = (Account)o;
59         return name.equals(other.name) && type.equals(other.type);
60     }
61 
hashCode()62     public int hashCode() {
63         int result = 17;
64         result = 31 * result + name.hashCode();
65         result = 31 * result + type.hashCode();
66         return result;
67     }
68 
Account(String name, String type)69     public Account(String name, String type) {
70         this(name, type, null);
71     }
72 
73     /**
74      * @hide
75      */
Account(@onNull Account other, @NonNull String accessId)76     public Account(@NonNull Account other, @NonNull String accessId) {
77         this(other.name, other.type, accessId);
78     }
79 
80     /**
81      * @hide
82      */
Account(String name, String type, String accessId)83     public Account(String name, String type, String accessId) {
84         if (TextUtils.isEmpty(name)) {
85             throw new IllegalArgumentException("the name must not be empty: " + name);
86         }
87         if (TextUtils.isEmpty(type)) {
88             throw new IllegalArgumentException("the type must not be empty: " + type);
89         }
90         if (name.length() > 200) {
91             throw new IllegalArgumentException("account name is longer than 200 characters");
92         }
93         if (type.length() > 200) {
94             throw new IllegalArgumentException("account type is longer than 200 characters");
95         }
96         this.name = name;
97         this.type = type;
98         this.accessId = accessId;
99     }
100 
Account(Parcel in)101     public Account(Parcel in) {
102         this.name = in.readString();
103         this.type = in.readString();
104         if (TextUtils.isEmpty(name)) {
105             throw new android.os.BadParcelableException("the name must not be empty: " + name);
106         }
107         if (TextUtils.isEmpty(type)) {
108             throw new android.os.BadParcelableException("the type must not be empty: " + type);
109         }
110         this.accessId = in.readString();
111         if (accessId != null) {
112             synchronized (sAccessedAccounts) {
113                 if (sAccessedAccounts.add(this)) {
114                     try {
115                         IAccountManager accountManager = IAccountManager.Stub.asInterface(
116                                 ServiceManager.getService(Context.ACCOUNT_SERVICE));
117                         accountManager.onAccountAccessed(accessId);
118                     } catch (RemoteException e) {
119                         Log.e(TAG, "Error noting account access", e);
120                     }
121                 }
122             }
123         }
124     }
125 
126     /** @hide */
getAccessId()127     public String getAccessId() {
128         return accessId;
129     }
130 
describeContents()131     public int describeContents() {
132         return 0;
133     }
134 
writeToParcel(Parcel dest, int flags)135     public void writeToParcel(Parcel dest, int flags) {
136         dest.writeString(name);
137         dest.writeString(type);
138         dest.writeString(accessId);
139     }
140 
141     public static final @android.annotation.NonNull Creator<Account> CREATOR = new Creator<Account>() {
142         public Account createFromParcel(Parcel source) {
143             return new Account(source);
144         }
145 
146         public Account[] newArray(int size) {
147             return new Account[size];
148         }
149     };
150 
toString()151     public String toString() {
152         return "Account {name=" + name + ", type=" + type + "}";
153     }
154 
155     /**
156      * Return a string representation of the account that is safe to print
157      * to logs and other places where PII should be avoided.
158      * @hide
159      */
toSafeString()160     public String toSafeString() {
161         if (mSafeName == null) {
162             mSafeName = toSafeName(name, 'x');
163         }
164         return "Account {name=" + mSafeName + ", type=" + type + "}";
165     }
166 
167     /**
168      * Given a name, replace all letter or digits with the replacement char.
169      * @param name The input name string.
170      * @param replacement the replacement character.
171      * @return the string after replacement.
172      * @hide
173      */
toSafeName(String name, char replacement)174     public static String toSafeName(String name, char replacement) {
175         final StringBuilder builder = new StringBuilder(64);
176         final int len = name.length();
177         for (int i = 0; i < len; i++) {
178             final char c = name.charAt(i);
179             if (Character.isLetterOrDigit(c)) {
180                 builder.append(replacement);
181             } else {
182                 builder.append(c);
183             }
184         }
185         return builder.toString();
186     }
187 }
188