1 /*
2  * Copyright (C) 2018 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.app;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.graphics.drawable.Icon;
22 import android.net.Uri;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 import java.util.Objects;
27 import java.util.function.Consumer;
28 
29 /**
30  * Provides an immutable reference to an entity that appears repeatedly on different surfaces of the
31  * platform. For example, this could represent the sender of a message.
32  */
33 public final class Person implements Parcelable {
34 
35     @Nullable private CharSequence mName;
36     @Nullable private Icon mIcon;
37     @Nullable private String mUri;
38     @Nullable private String mKey;
39     private boolean mIsBot;
40     private boolean mIsImportant;
41 
Person(Parcel in)42     private Person(Parcel in) {
43         mName = in.readCharSequence();
44         if (in.readInt() != 0) {
45             mIcon = Icon.CREATOR.createFromParcel(in);
46         }
47         mUri = in.readString();
48         mKey = in.readString();
49         mIsImportant = in.readBoolean();
50         mIsBot = in.readBoolean();
51     }
52 
Person(Builder builder)53     private Person(Builder builder) {
54         mName = builder.mName;
55         mIcon = builder.mIcon;
56         mUri = builder.mUri;
57         mKey = builder.mKey;
58         mIsBot = builder.mIsBot;
59         mIsImportant = builder.mIsImportant;
60     }
61 
62     /** Creates and returns a new {@link Builder} initialized with this Person's data. */
toBuilder()63     public Builder toBuilder() {
64         return new Builder(this);
65     }
66 
67     /**
68      * @return the uri provided for this person or {@code null} if no Uri was provided.
69      */
70     @Nullable
getUri()71     public String getUri() {
72         return mUri;
73     }
74 
75     /**
76      * @return the name provided for this person or {@code null} if no name was provided.
77      */
78     @Nullable
getName()79     public CharSequence getName() {
80         return mName;
81     }
82 
83     /**
84      * @return the icon provided for this person or {@code null} if no icon was provided.
85      */
86     @Nullable
getIcon()87     public Icon getIcon() {
88         return mIcon;
89     }
90 
91     /**
92      * @return the key provided for this person or {@code null} if no key was provided.
93      */
94     @Nullable
getKey()95     public String getKey() {
96         return mKey;
97     }
98 
99     /**
100      * @return whether this Person is a machine.
101      */
isBot()102     public boolean isBot() {
103         return mIsBot;
104     }
105 
106     /**
107      * @return whether this Person is important.
108      */
isImportant()109     public boolean isImportant() {
110         return mIsImportant;
111     }
112 
113     /**
114      * @return the URI associated with this person, or "name:mName" otherwise
115      *  @hide
116      */
resolveToLegacyUri()117     public String resolveToLegacyUri() {
118         if (mUri != null) {
119             return mUri;
120         }
121         if (mName != null) {
122             return "name:" + mName;
123         }
124         return "";
125     }
126 
127     /**
128      * @return the URI associated with the {@link #getIcon()} for this person, iff the icon exists
129      * and is URI based.
130      * @hide
131      */
132     @Nullable
getIconUri()133     public Uri getIconUri() {
134         if (mIcon != null && (mIcon.getType() == Icon.TYPE_URI
135                 || mIcon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
136             return mIcon.getUri();
137         }
138         return null;
139     }
140 
141     @Override
equals(@ullable Object obj)142     public boolean equals(@Nullable Object obj) {
143         if (obj instanceof Person) {
144             final Person other = (Person) obj;
145             return Objects.equals(mName, other.mName)
146                     && (mIcon == null ? other.mIcon == null :
147                     (other.mIcon != null && mIcon.sameAs(other.mIcon)))
148                     && Objects.equals(mUri, other.mUri)
149                     && Objects.equals(mKey, other.mKey)
150                     && mIsBot == other.mIsBot
151                     && mIsImportant == other.mIsImportant;
152         }
153         return false;
154     }
155 
156     @Override
hashCode()157     public int hashCode() {
158         return Objects.hash(mName, mIcon, mUri, mKey, mIsBot, mIsImportant);
159     }
160 
161     @Override
describeContents()162     public int describeContents() {
163         return 0;
164     }
165 
166     @Override
writeToParcel(Parcel dest, @WriteFlags int flags)167     public void writeToParcel(Parcel dest, @WriteFlags int flags) {
168         dest.writeCharSequence(mName);
169         if (mIcon != null) {
170             dest.writeInt(1);
171             mIcon.writeToParcel(dest, 0);
172         } else {
173             dest.writeInt(0);
174         }
175         dest.writeString(mUri);
176         dest.writeString(mKey);
177         dest.writeBoolean(mIsImportant);
178         dest.writeBoolean(mIsBot);
179     }
180 
181     /**
182      * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
183      * grants will need to be issued to ensure the recipient of this object is able to render its
184      * contents.
185      * See b/281044385 for more context and examples about what happens when this isn't done
186      * correctly.
187      *
188      * @hide
189      */
visitUris(@onNull Consumer<Uri> visitor)190     public void visitUris(@NonNull Consumer<Uri> visitor) {
191         visitor.accept(getIconUri());
192     }
193 
194     /** Builder for the immutable {@link Person} class. */
195     public static class Builder {
196         @Nullable private CharSequence mName;
197         @Nullable private Icon mIcon;
198         @Nullable private String mUri;
199         @Nullable private String mKey;
200         private boolean mIsBot;
201         private boolean mIsImportant;
202 
203         /** Creates a new, empty {@link Builder}. */
Builder()204         public Builder() {
205         }
206 
Builder(Person person)207         private Builder(Person person) {
208             mName = person.mName;
209             mIcon = person.mIcon;
210             mUri = person.mUri;
211             mKey = person.mKey;
212             mIsBot = person.mIsBot;
213             mIsImportant = person.mIsImportant;
214         }
215 
216         /**
217          * Give this person a name.
218          *
219          * @param name the name of this person.
220          */
221         @NonNull
setName(@ullable CharSequence name)222         public Person.Builder setName(@Nullable CharSequence name) {
223             this.mName = name;
224             return this;
225         }
226 
227         /**
228          * Add an icon for this person.
229          * <br />
230          * The system will prefer this icon over any images that are resolved from the URI.
231          *
232          * @param icon the icon of the person.
233          */
234         @NonNull
setIcon(@ullable Icon icon)235         public Person.Builder setIcon(@Nullable Icon icon) {
236             this.mIcon = icon;
237             return this;
238         }
239 
240         /**
241          * Set a URI associated with this person.
242          *
243          * <P>
244          * The person should be specified by the {@code String} representation of a
245          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
246          * </P>
247          *
248          * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
249          * URIs. The path part of these URIs must exist in the contacts database, in the
250          * appropriate column, or the reference will be discarded as invalid. Telephone schema
251          * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
252          * </P>
253          *
254          * @param uri a URI for the person.
255          */
256         @NonNull
setUri(@ullable String uri)257         public Person.Builder setUri(@Nullable String uri) {
258             mUri = uri;
259             return this;
260         }
261 
262         /**
263          * Add a key to this person in order to uniquely identify it.
264          * This is especially useful if the name doesn't uniquely identify this person or if the
265          * display name is a short handle of the actual name.
266          *
267          * <P>If no key is provided, the name serves as the key for the purpose of
268          * identification.</P>
269          *
270          * @param key the key that uniquely identifies this person.
271          */
272         @NonNull
setKey(@ullable String key)273         public Person.Builder setKey(@Nullable String key) {
274             mKey = key;
275             return this;
276         }
277 
278         /**
279          * Sets whether this is an important person. Use this method to denote users who frequently
280          * interact with the user of this device when {@link #setUri(String)} isn't provided with
281          * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, and instead with
282          * the {@code mailto:} or {@code tel:} schemas.
283          *
284          * @param isImportant {@code true} if this is an important person, {@code false} otherwise.
285          */
286         @NonNull
setImportant(boolean isImportant)287         public Person.Builder setImportant(boolean isImportant) {
288             mIsImportant = isImportant;
289             return this;
290         }
291 
292         /**
293          * Sets whether this person is a machine rather than a human.
294          *
295          * @param isBot {@code true} if this person is a machine, {@code false} otherwise.
296          */
297         @NonNull
setBot(boolean isBot)298         public Person.Builder setBot(boolean isBot) {
299             mIsBot = isBot;
300             return this;
301         }
302 
303         /** Creates and returns the {@link Person} this builder represents. */
304         @NonNull
build()305         public Person build() {
306             return new Person(this);
307         }
308     }
309 
310     public static final @android.annotation.NonNull Creator<Person> CREATOR = new Creator<Person>() {
311         @Override
312         public Person createFromParcel(Parcel in) {
313             return new Person(in);
314         }
315 
316         @Override
317         public Person[] newArray(int size) {
318             return new Person[size];
319         }
320     };
321 }
322