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