• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.vending.sectool.v1;
18 
19 import android.content.ContentResolver;
20 import android.database.ContentObserver;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.util.Log;
26 
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.TreeMap;
30 import java.util.regex.Pattern;
31 
32 /**
33  * Gservices provides access to a key-value store that is can be
34  * updated remote (by the google checkin service).
35  */
36 public class Gservices {
37     public static final String TAG = "Gservices";
38 
39     public static final String OVERRIDE_ACTION =
40         "com.google.gservices.intent.action.GSERVICES_OVERRIDE";
41     public static final String CHANGED_ACTION =
42         "com.google.gservices.intent.action.GSERVICES_CHANGED";
43 
44     public final static Uri CONTENT_URI =
45         Uri.parse("content://com.google.android.gsf.gservices");
46     public final static Uri CONTENT_PREFIX_URI =
47         Uri.parse("content://com.google.android.gsf.gservices/prefix");
48 
49     public static final Pattern TRUE_PATTERN =
50         Pattern.compile("^(1|true|t|on|yes|y)$", Pattern.CASE_INSENSITIVE);
51     public static final Pattern FALSE_PATTERN =
52         Pattern.compile("^(0|false|f|off|no|n)$", Pattern.CASE_INSENSITIVE);
53 
54     private static ContentResolver sResolver;
55     private static HashMap<String, String> sCache;
56     private static Object sVersionToken;
57 
ensureCacheInitializedLocked(final ContentResolver cr)58     private static void ensureCacheInitializedLocked(final ContentResolver cr) {
59         if (sCache == null) {
60             sCache = new HashMap<String, String>();
61             sVersionToken = new Object();
62             sResolver = cr;
63 
64             // Create a thread to host a Handler for ContentObserver callbacks.
65             // The callback will clear the cache to force the resolver to be consulted
66             // on future gets. The version is also updated.
67             new Thread() {
68                 public void run() {
69                     Looper.prepare();
70                     cr.registerContentObserver(CONTENT_URI, true,
71                         new ContentObserver(new Handler(Looper.myLooper())) {
72                             public void onChange(boolean selfChange) {
73                                 synchronized (Gservices.class) {
74                                     sCache.clear();
75                                     sVersionToken = new Object();
76                                 }
77                             } });
78                     Looper.loop();
79                 }
80             }.start();
81         }
82     }
83 
84     /**
85      * Look up a key in the database.
86      * @param cr to access the database with
87      * @param key to look up in the table
88      * @param defValue the value to return if the value from the database is null
89      * @return the corresponding value, or defValue if not present
90      */
getString(ContentResolver cr, String key, String defValue)91     public static String getString(ContentResolver cr, String key, String defValue) {
92         final Object version;
93         synchronized (Gservices.class) {
94             ensureCacheInitializedLocked(cr);
95             version = sVersionToken;
96             if (sCache.containsKey(key)) {
97                 String value = sCache.get(key);
98                 return (value != null) ? value : defValue;
99             }
100         }
101         Cursor cursor = sResolver.query(CONTENT_URI, null, null, new String[]{ key }, null);
102         if (cursor == null) return defValue;
103 
104         try {
105             cursor.moveToFirst();
106             String value = cursor.getString(1);
107             synchronized (Gservices.class) {
108                 // There is a chance that the version change, and thus the cache clearing,
109                 // happened after the query, meaning the value we got could be stale. Don't
110                 // store it in the cache in this case.
111                 if (version == sVersionToken) {
112                     sCache.put(key, value);
113                 }
114             }
115             return (value != null) ? value : defValue;
116         } finally {
117             cursor.close();
118         }
119     }
120 
121     /**
122      * Look up a key in the database.
123      * @param cr to access the database with
124      * @param key to look up in the table
125      * @return the corresponding value, or null if not present
126      */
getString(ContentResolver cr, String key)127     public static String getString(ContentResolver cr, String key) {
128         return getString(cr, key, null);
129     }
130 
131     /**
132      * Look up the value for key in the database, convert it to an int
133      * using Integer.parseInt and return it. If it is null or if a
134      * NumberFormatException is caught during the conversion then
135      * return defValue.
136      */
getInt(ContentResolver cr, String key, int defValue)137     public static int getInt(ContentResolver cr, String key, int defValue) {
138         String valString = getString(cr, key);
139         int value;
140         try {
141             value = valString != null ? Integer.parseInt(valString) : defValue;
142         } catch (NumberFormatException e) {
143             value = defValue;
144         }
145         return value;
146     }
147 
148     /**
149      * Look up the value for key in the database, convert it to a long
150      * using Long.parseLong and return it. If it is null or if a
151      * NumberFormatException is caught during the conversion then
152      * return defValue.
153      */
getLong(ContentResolver cr, String key, long defValue)154     public static long getLong(ContentResolver cr, String key, long defValue) {
155         String valString = getString(cr, key);
156         long value;
157         try {
158             value = valString != null ? Long.parseLong(valString) : defValue;
159         } catch (NumberFormatException e) {
160             value = defValue;
161         }
162         return value;
163     }
164 
getBoolean(ContentResolver cr, String key, boolean defValue)165     public static boolean getBoolean(ContentResolver cr, String key, boolean defValue) {
166         String valString = getString(cr, key);
167         if (valString == null || valString.equals("")) {
168             return defValue;
169         } else if (TRUE_PATTERN.matcher(valString).matches()) {
170             return true;
171         } else if (FALSE_PATTERN.matcher(valString).matches()) {
172             return false;
173         } else {
174             // Log a possible app bug
175             Log.w(TAG, "attempt to read gservices key " + key + " (value \"" +
176                   valString + "\") as boolean");
177             return defValue;
178         }
179     }
180 
181     /**
182      * Look up values for all keys beginning with any of the given prefixes.
183      *
184      * @return a Map<String, String> of the matching key-value pairs.
185      */
getStringsByPrefix(ContentResolver cr, String... prefixes)186     public static Map<String, String> getStringsByPrefix(ContentResolver cr,
187                                                          String... prefixes) {
188         Cursor c = cr.query(CONTENT_PREFIX_URI, null, null, prefixes, null);
189         TreeMap<String, String> out = new TreeMap<String, String>();
190         if (c == null) return out;
191 
192         try {
193             while (c.moveToNext()) {
194                 out.put(c.getString(0), c.getString(1));
195             }
196         } finally {
197             c.close();
198         }
199         return out;
200     }
201 
202     /**
203      * Returns a token that represents the current version of the data within gservices
204      * @param cr the ContentResolver that Gservices should use to fill its cache
205      * @return an Object that represents the current version of the Gservices values.
206      */
getVersionToken(ContentResolver cr)207     public static Object getVersionToken(ContentResolver cr) {
208         synchronized (Gservices.class) {
209             // Even though we don't need the cache itself, we need the cache version, so we make
210             // that the cache has been initialized before we return its version.
211             ensureCacheInitializedLocked(cr);
212             return sVersionToken;
213         }
214     }
215 }
216