1 /*
2  * Copyright (C) 2007 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.content;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.util.ArrayMap;
23 import android.util.Log;
24 
25 import com.android.internal.util.Preconditions;
26 
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Objects;
31 import java.util.Set;
32 
33 /**
34  * This class is used to store a set of values that the {@link ContentResolver}
35  * can process.
36  */
37 public final class ContentValues implements Parcelable {
38     public static final String TAG = "ContentValues";
39 
40     /**
41      * @hide
42      * @deprecated kept around for lame people doing reflection
43      */
44     @Deprecated
45     @UnsupportedAppUsage
46     private HashMap<String, Object> mValues;
47 
48     private final ArrayMap<String, Object> mMap;
49 
50     /**
51      * Creates an empty set of values using the default initial size
52      */
ContentValues()53     public ContentValues() {
54         mMap = new ArrayMap<>();
55     }
56 
57     /**
58      * Creates an empty set of values using the given initial size
59      *
60      * @param size the initial size of the set of values
61      */
ContentValues(int size)62     public ContentValues(int size) {
63         Preconditions.checkArgumentNonnegative(size);
64         mMap = new ArrayMap<>(size);
65     }
66 
67     /**
68      * Creates a set of values copied from the given set
69      *
70      * @param from the values to copy
71      */
ContentValues(ContentValues from)72     public ContentValues(ContentValues from) {
73         Objects.requireNonNull(from);
74         mMap = new ArrayMap<>(from.mMap);
75     }
76 
77     /**
78      * @hide
79      * @deprecated kept around for lame people doing reflection
80      */
81     @Deprecated
82     @UnsupportedAppUsage
ContentValues(HashMap<String, Object> from)83     private ContentValues(HashMap<String, Object> from) {
84         mMap = new ArrayMap<>();
85         mMap.putAll(from);
86     }
87 
88     /** {@hide} */
ContentValues(Parcel in)89     private ContentValues(Parcel in) {
90         mMap = new ArrayMap<>(in.readInt());
91         in.readArrayMap(mMap, null);
92     }
93 
94     @Override
equals(Object object)95     public boolean equals(Object object) {
96         if (!(object instanceof ContentValues)) {
97             return false;
98         }
99         return mMap.equals(((ContentValues) object).mMap);
100     }
101 
102     /** {@hide} */
getValues()103     public ArrayMap<String, Object> getValues() {
104         return mMap;
105     }
106 
107     @Override
hashCode()108     public int hashCode() {
109         return mMap.hashCode();
110     }
111 
112     /**
113      * Adds a value to the set.
114      *
115      * @param key the name of the value to put
116      * @param value the data for the value to put
117      */
put(String key, String value)118     public void put(String key, String value) {
119         mMap.put(key, value);
120     }
121 
122     /**
123      * Adds all values from the passed in ContentValues.
124      *
125      * @param other the ContentValues from which to copy
126      */
putAll(ContentValues other)127     public void putAll(ContentValues other) {
128         mMap.putAll(other.mMap);
129     }
130 
131     /**
132      * Adds a value to the set.
133      *
134      * @param key the name of the value to put
135      * @param value the data for the value to put
136      */
put(String key, Byte value)137     public void put(String key, Byte value) {
138         mMap.put(key, value);
139     }
140 
141     /**
142      * Adds a value to the set.
143      *
144      * @param key the name of the value to put
145      * @param value the data for the value to put
146      */
put(String key, Short value)147     public void put(String key, Short value) {
148         mMap.put(key, value);
149     }
150 
151     /**
152      * Adds a value to the set.
153      *
154      * @param key the name of the value to put
155      * @param value the data for the value to put
156      */
put(String key, Integer value)157     public void put(String key, Integer value) {
158         mMap.put(key, value);
159     }
160 
161     /**
162      * Adds a value to the set.
163      *
164      * @param key the name of the value to put
165      * @param value the data for the value to put
166      */
put(String key, Long value)167     public void put(String key, Long value) {
168         mMap.put(key, value);
169     }
170 
171     /**
172      * Adds a value to the set.
173      *
174      * @param key the name of the value to put
175      * @param value the data for the value to put
176      */
put(String key, Float value)177     public void put(String key, Float value) {
178         mMap.put(key, value);
179     }
180 
181     /**
182      * Adds a value to the set.
183      *
184      * @param key the name of the value to put
185      * @param value the data for the value to put
186      */
put(String key, Double value)187     public void put(String key, Double value) {
188         mMap.put(key, value);
189     }
190 
191     /**
192      * Adds a value to the set.
193      *
194      * @param key the name of the value to put
195      * @param value the data for the value to put
196      */
put(String key, Boolean value)197     public void put(String key, Boolean value) {
198         mMap.put(key, value);
199     }
200 
201     /**
202      * Adds a value to the set.
203      *
204      * @param key the name of the value to put
205      * @param value the data for the value to put
206      */
put(String key, byte[] value)207     public void put(String key, byte[] value) {
208         mMap.put(key, value);
209     }
210 
211     /**
212      * Adds a null value to the set.
213      *
214      * @param key the name of the value to make null
215      */
putNull(String key)216     public void putNull(String key) {
217         mMap.put(key, null);
218     }
219 
220     /**
221      * Returns the number of values.
222      *
223      * @return the number of values
224      */
size()225     public int size() {
226         return mMap.size();
227     }
228 
229     /**
230      * Indicates whether this collection is empty.
231      *
232      * @return true iff size == 0
233      * {@hide}
234      * TODO: consider exposing this new method publicly
235      */
isEmpty()236     public boolean isEmpty() {
237         return mMap.isEmpty();
238     }
239 
240     /**
241      * Remove a single value.
242      *
243      * @param key the name of the value to remove
244      */
remove(String key)245     public void remove(String key) {
246         mMap.remove(key);
247     }
248 
249     /**
250      * Removes all values.
251      */
clear()252     public void clear() {
253         mMap.clear();
254     }
255 
256     /**
257      * Returns true if this object has the named value.
258      *
259      * @param key the value to check for
260      * @return {@code true} if the value is present, {@code false} otherwise
261      */
containsKey(String key)262     public boolean containsKey(String key) {
263         return mMap.containsKey(key);
264     }
265 
266     /**
267      * Gets a value. Valid value types are {@link String}, {@link Boolean},
268      * {@link Number}, and {@code byte[]} implementations.
269      *
270      * @param key the value to get
271      * @return the data for the value, or {@code null} if the value is missing or if {@code null}
272      *         was previously added with the given {@code key}
273      */
get(String key)274     public Object get(String key) {
275         return mMap.get(key);
276     }
277 
278     /**
279      * Gets a value and converts it to a String.
280      *
281      * @param key the value to get
282      * @return the String for the value
283      */
getAsString(String key)284     public String getAsString(String key) {
285         Object value = mMap.get(key);
286         return value != null ? value.toString() : null;
287     }
288 
289     /**
290      * Gets a value and converts it to a Long.
291      *
292      * @param key the value to get
293      * @return the Long value, or {@code null} if the value is missing or cannot be converted
294      */
getAsLong(String key)295     public Long getAsLong(String key) {
296         Object value = mMap.get(key);
297         try {
298             return value != null ? ((Number) value).longValue() : null;
299         } catch (ClassCastException e) {
300             if (value instanceof CharSequence) {
301                 try {
302                     return Long.valueOf(value.toString());
303                 } catch (NumberFormatException e2) {
304                     Log.e(TAG, "Cannot parse Long value for " + value + " at key " + key);
305                     return null;
306                 }
307             } else {
308                 Log.e(TAG, "Cannot cast value for " + key + " to a Long: " + value, e);
309                 return null;
310             }
311         }
312     }
313 
314     /**
315      * Gets a value and converts it to an Integer.
316      *
317      * @param key the value to get
318      * @return the Integer value, or {@code null} if the value is missing or cannot be converted
319      */
getAsInteger(String key)320     public Integer getAsInteger(String key) {
321         Object value = mMap.get(key);
322         try {
323             return value != null ? ((Number) value).intValue() : null;
324         } catch (ClassCastException e) {
325             if (value instanceof CharSequence) {
326                 try {
327                     return Integer.valueOf(value.toString());
328                 } catch (NumberFormatException e2) {
329                     Log.e(TAG, "Cannot parse Integer value for " + value + " at key " + key);
330                     return null;
331                 }
332             } else {
333                 Log.e(TAG, "Cannot cast value for " + key + " to a Integer: " + value, e);
334                 return null;
335             }
336         }
337     }
338 
339     /**
340      * Gets a value and converts it to a Short.
341      *
342      * @param key the value to get
343      * @return the Short value, or {@code null} if the value is missing or cannot be converted
344      */
getAsShort(String key)345     public Short getAsShort(String key) {
346         Object value = mMap.get(key);
347         try {
348             return value != null ? ((Number) value).shortValue() : null;
349         } catch (ClassCastException e) {
350             if (value instanceof CharSequence) {
351                 try {
352                     return Short.valueOf(value.toString());
353                 } catch (NumberFormatException e2) {
354                     Log.e(TAG, "Cannot parse Short value for " + value + " at key " + key);
355                     return null;
356                 }
357             } else {
358                 Log.e(TAG, "Cannot cast value for " + key + " to a Short: " + value, e);
359                 return null;
360             }
361         }
362     }
363 
364     /**
365      * Gets a value and converts it to a Byte.
366      *
367      * @param key the value to get
368      * @return the Byte value, or {@code null} if the value is missing or cannot be converted
369      */
getAsByte(String key)370     public Byte getAsByte(String key) {
371         Object value = mMap.get(key);
372         try {
373             return value != null ? ((Number) value).byteValue() : null;
374         } catch (ClassCastException e) {
375             if (value instanceof CharSequence) {
376                 try {
377                     return Byte.valueOf(value.toString());
378                 } catch (NumberFormatException e2) {
379                     Log.e(TAG, "Cannot parse Byte value for " + value + " at key " + key);
380                     return null;
381                 }
382             } else {
383                 Log.e(TAG, "Cannot cast value for " + key + " to a Byte: " + value, e);
384                 return null;
385             }
386         }
387     }
388 
389     /**
390      * Gets a value and converts it to a Double.
391      *
392      * @param key the value to get
393      * @return the Double value, or {@code null} if the value is missing or cannot be converted
394      */
getAsDouble(String key)395     public Double getAsDouble(String key) {
396         Object value = mMap.get(key);
397         try {
398             return value != null ? ((Number) value).doubleValue() : null;
399         } catch (ClassCastException e) {
400             if (value instanceof CharSequence) {
401                 try {
402                     return Double.valueOf(value.toString());
403                 } catch (NumberFormatException e2) {
404                     Log.e(TAG, "Cannot parse Double value for " + value + " at key " + key);
405                     return null;
406                 }
407             } else {
408                 Log.e(TAG, "Cannot cast value for " + key + " to a Double: " + value, e);
409                 return null;
410             }
411         }
412     }
413 
414     /**
415      * Gets a value and converts it to a Float.
416      *
417      * @param key the value to get
418      * @return the Float value, or {@code null} if the value is missing or cannot be converted
419      */
getAsFloat(String key)420     public Float getAsFloat(String key) {
421         Object value = mMap.get(key);
422         try {
423             return value != null ? ((Number) value).floatValue() : null;
424         } catch (ClassCastException e) {
425             if (value instanceof CharSequence) {
426                 try {
427                     return Float.valueOf(value.toString());
428                 } catch (NumberFormatException e2) {
429                     Log.e(TAG, "Cannot parse Float value for " + value + " at key " + key);
430                     return null;
431                 }
432             } else {
433                 Log.e(TAG, "Cannot cast value for " + key + " to a Float: " + value, e);
434                 return null;
435             }
436         }
437     }
438 
439     /**
440      * Gets a value and converts it to a Boolean.
441      *
442      * @param key the value to get
443      * @return the Boolean value, or {@code null} if the value is missing or cannot be converted
444      */
getAsBoolean(String key)445     public Boolean getAsBoolean(String key) {
446         Object value = mMap.get(key);
447         try {
448             return (Boolean) value;
449         } catch (ClassCastException e) {
450             if (value instanceof CharSequence) {
451                 // Note that we also check against 1 here because SQLite's internal representation
452                 // for booleans is an integer with a value of 0 or 1. Without this check, boolean
453                 // values obtained via DatabaseUtils#cursorRowToContentValues will always return
454                 // false.
455                 return Boolean.valueOf(value.toString()) || "1".equals(value);
456             } else if (value instanceof Number) {
457                 return ((Number) value).intValue() != 0;
458             } else {
459                 Log.e(TAG, "Cannot cast value for " + key + " to a Boolean: " + value, e);
460                 return null;
461             }
462         }
463     }
464 
465     /**
466      * Gets a value that is a byte array. Note that this method will not convert
467      * any other types to byte arrays.
468      *
469      * @param key the value to get
470      * @return the {@code byte[]} value, or {@code null} is the value is missing or not a
471      *         {@code byte[]}
472      */
getAsByteArray(String key)473     public byte[] getAsByteArray(String key) {
474         Object value = mMap.get(key);
475         if (value instanceof byte[]) {
476             return (byte[]) value;
477         } else {
478             return null;
479         }
480     }
481 
482     /**
483      * Returns a set of all of the keys and values
484      *
485      * @return a set of all of the keys and values
486      */
valueSet()487     public Set<Map.Entry<String, Object>> valueSet() {
488         return mMap.entrySet();
489     }
490 
491     /**
492      * Returns a set of all of the keys
493      *
494      * @return a set of all of the keys
495      */
keySet()496     public Set<String> keySet() {
497         return mMap.keySet();
498     }
499 
500     public static final @android.annotation.NonNull Parcelable.Creator<ContentValues> CREATOR =
501             new Parcelable.Creator<ContentValues>() {
502         @Override
503         public ContentValues createFromParcel(Parcel in) {
504             return new ContentValues(in);
505         }
506 
507         @Override
508         public ContentValues[] newArray(int size) {
509             return new ContentValues[size];
510         }
511     };
512 
513     @Override
describeContents()514     public int describeContents() {
515         return 0;
516     }
517 
518     @Override
writeToParcel(Parcel parcel, int flags)519     public void writeToParcel(Parcel parcel, int flags) {
520         parcel.writeInt(mMap.size());
521         parcel.writeArrayMap(mMap);
522     }
523 
524     /**
525      * Unsupported, here until we get proper bulk insert APIs.
526      * {@hide}
527      */
528     @Deprecated
529     @UnsupportedAppUsage
putStringArrayList(String key, ArrayList<String> value)530     public void putStringArrayList(String key, ArrayList<String> value) {
531         mMap.put(key, value);
532     }
533 
534     /**
535      * Unsupported, here until we get proper bulk insert APIs.
536      * {@hide}
537      */
538     @SuppressWarnings("unchecked")
539     @Deprecated
540     @UnsupportedAppUsage
getStringArrayList(String key)541     public ArrayList<String> getStringArrayList(String key) {
542         return (ArrayList<String>) mMap.get(key);
543     }
544 
545     /**
546      * Returns a string containing a concise, human-readable description of this object.
547      * @return a printable representation of this object.
548      */
549     @Override
toString()550     public String toString() {
551         StringBuilder sb = new StringBuilder();
552         for (String name : mMap.keySet()) {
553             String value = getAsString(name);
554             if (sb.length() > 0) sb.append(" ");
555             sb.append(name + "=" + value);
556         }
557         return sb.toString();
558     }
559 }
560