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.Set;
35 
36 /**
37  * Value type that represents an Account in the {@link AccountManager}. This object is
38  * {@link Parcelable} and also overrides {@link #equals} and {@link #hashCode}, making it
39  * suitable for use as the key of a {@link java.util.Map}
40  */
41 @android.ravenwood.annotation.RavenwoodKeepWholeClass
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 @NonNull String name;
50     public final @NonNull 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(@onNull String name, @NonNull String type)69     public Account(@NonNull String name, @NonNull 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(@onNull String name, @NonNull String type, String accessId)83     public Account(@NonNull String name, @NonNull 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         this.name = name;
91         this.type = type;
92         this.accessId = accessId;
93     }
94 
Account(Parcel in)95     public Account(Parcel in) {
96         this.name = in.readString();
97         this.type = in.readString();
98         if (TextUtils.isEmpty(name)) {
99             throw new android.os.BadParcelableException("the name must not be empty: " + name);
100         }
101         if (TextUtils.isEmpty(type)) {
102             throw new android.os.BadParcelableException("the type must not be empty: " + type);
103         }
104         this.accessId = in.readString();
105         if (accessId != null) {
106             synchronized (sAccessedAccounts) {
107                 if (sAccessedAccounts.add(this)) {
108                     onAccountAccessed(accessId);
109                 }
110             }
111         }
112     }
113 
114     @android.ravenwood.annotation.RavenwoodReplace
onAccountAccessed(String accessId)115     private static void onAccountAccessed(String accessId) {
116         try {
117             IAccountManager accountManager = IAccountManager.Stub.asInterface(
118                     ServiceManager.getService(Context.ACCOUNT_SERVICE));
119             accountManager.onAccountAccessed(accessId);
120         } catch (RemoteException e) {
121             Log.e(TAG, "Error noting account access", e);
122         }
123     }
124 
onAccountAccessed$ravenwood(String accessId)125     private static void onAccountAccessed$ravenwood(String accessId) {
126         // No AccountManager to communicate with; ignored
127     }
128 
129     /** @hide */
getAccessId()130     public String getAccessId() {
131         return accessId;
132     }
133 
describeContents()134     public int describeContents() {
135         return 0;
136     }
137 
writeToParcel(Parcel dest, int flags)138     public void writeToParcel(Parcel dest, int flags) {
139         dest.writeString(name);
140         dest.writeString(type);
141         dest.writeString(accessId);
142     }
143 
144     public static final @android.annotation.NonNull Creator<Account> CREATOR = new Creator<Account>() {
145         public Account createFromParcel(Parcel source) {
146             return new Account(source);
147         }
148 
149         public Account[] newArray(int size) {
150             return new Account[size];
151         }
152     };
153 
toString()154     public String toString() {
155         return "Account {name=" + name + ", type=" + type + "}";
156     }
157 
158     /**
159      * Return a string representation of the account that is safe to print
160      * to logs and other places where PII should be avoided.
161      * @hide
162      */
toSafeString()163     public String toSafeString() {
164         if (mSafeName == null) {
165             mSafeName = toSafeName(name, 'x');
166         }
167         return "Account {name=" + mSafeName + ", type=" + type + "}";
168     }
169 
170     /**
171      * Given a name, replace all letter or digits with the replacement char.
172      * @param name The input name string.
173      * @param replacement the replacement character.
174      * @return the string after replacement.
175      * @hide
176      */
toSafeName(String name, char replacement)177     public static String toSafeName(String name, char replacement) {
178         final StringBuilder builder = new StringBuilder(64);
179         final int len = name.length();
180         for (int i = 0; i < len; i++) {
181             final char c = name.charAt(i);
182             if (Character.isLetterOrDigit(c)) {
183                 builder.append(replacement);
184             } else {
185                 builder.append(c);
186             }
187         }
188         return builder.toString();
189     }
190 }
191