CallLog.java revision 3b444db1d6cbc539a3c4ed653e4e675eb42d63bf
1/*
2 * Copyright (C) 2006 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
18package android.provider;
19
20import android.content.ContentProvider;
21import android.content.ContentResolver;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.UserInfo;
26import android.database.Cursor;
27import android.location.Country;
28import android.location.CountryDetector;
29import android.net.Uri;
30import android.os.UserHandle;
31import android.os.UserManager;
32import android.provider.ContactsContract.CommonDataKinds.Callable;
33import android.provider.ContactsContract.CommonDataKinds.Phone;
34import android.provider.ContactsContract.Data;
35import android.provider.ContactsContract.DataUsageFeedback;
36import android.telecom.PhoneAccount;
37import android.telecom.PhoneAccountHandle;
38import android.telecom.TelecomManager;
39import android.telephony.PhoneNumberUtils;
40import android.text.TextUtils;
41import android.util.Log;
42
43import com.android.internal.telephony.CallerInfo;
44import com.android.internal.telephony.PhoneConstants;
45
46import java.util.List;
47
48/**
49 * The CallLog provider contains information about placed and received calls.
50 */
51public class CallLog {
52    private static final String LOG_TAG = "CallLog";
53
54    public static final String AUTHORITY = "call_log";
55
56    /**
57     * The content:// style URL for this provider
58     */
59    public static final Uri CONTENT_URI =
60        Uri.parse("content://" + AUTHORITY);
61
62    /**
63     * Contains the recent calls.
64     */
65    public static class Calls implements BaseColumns {
66        /**
67         * The content:// style URL for this table
68         */
69        public static final Uri CONTENT_URI =
70                Uri.parse("content://call_log/calls");
71
72        /**
73         * The content:// style URL for filtering this table on phone numbers
74         */
75        public static final Uri CONTENT_FILTER_URI =
76                Uri.parse("content://call_log/calls/filter");
77
78        /**
79         * Query parameter used to limit the number of call logs returned.
80         * <p>
81         * TYPE: integer
82         */
83        public static final String LIMIT_PARAM_KEY = "limit";
84
85        /**
86         * Query parameter used to specify the starting record to return.
87         * <p>
88         * TYPE: integer
89         */
90        public static final String OFFSET_PARAM_KEY = "offset";
91
92        /**
93         * An optional URI parameter which instructs the provider to allow the operation to be
94         * applied to voicemail records as well.
95         * <p>
96         * TYPE: Boolean
97         * <p>
98         * Using this parameter with a value of {@code true} will result in a security error if the
99         * calling package does not have appropriate permissions to access voicemails.
100         *
101         * @hide
102         */
103        public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails";
104
105        /**
106         * An optional extra used with {@link #CONTENT_TYPE Calls.CONTENT_TYPE} and
107         * {@link Intent#ACTION_VIEW} to specify that the presented list of calls should be
108         * filtered for a particular call type.
109         *
110         * Applications implementing a call log UI should check for this extra, and display a
111         * filtered list of calls based on the specified call type. If not applicable within the
112         * application's UI, it should be silently ignored.
113         *
114         * <p>
115         * The following example brings up the call log, showing only missed calls.
116         * <pre>
117         * Intent intent = new Intent(Intent.ACTION_VIEW);
118         * intent.setType(CallLog.Calls.CONTENT_TYPE);
119         * intent.putExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, CallLog.Calls.MISSED_TYPE);
120         * startActivity(intent);
121         * </pre>
122         * </p>
123         */
124        public static final String EXTRA_CALL_TYPE_FILTER =
125                "android.provider.extra.CALL_TYPE_FILTER";
126
127        /**
128         * Content uri used to access call log entries, including voicemail records. You must have
129         * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log, as
130         * well as READ_VOICEMAIL and WRITE_VOICEMAIL permissions to read and write voicemails.
131         */
132        public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon()
133                .appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true")
134                .build();
135
136        /**
137         * The default sort order for this table
138         */
139        public static final String DEFAULT_SORT_ORDER = "date DESC";
140
141        /**
142         * The MIME type of {@link #CONTENT_URI} and {@link #CONTENT_FILTER_URI}
143         * providing a directory of calls.
144         */
145        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/calls";
146
147        /**
148         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single
149         * call.
150         */
151        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls";
152
153        /**
154         * The type of the call (incoming, outgoing or missed).
155         * <P>Type: INTEGER (int)</P>
156         */
157        public static final String TYPE = "type";
158
159        /** Call log type for incoming calls. */
160        public static final int INCOMING_TYPE = 1;
161        /** Call log type for outgoing calls. */
162        public static final int OUTGOING_TYPE = 2;
163        /** Call log type for missed calls. */
164        public static final int MISSED_TYPE = 3;
165        /** Call log type for voicemails. */
166        public static final int VOICEMAIL_TYPE = 4;
167
168        /**
169         * Bit-mask describing features of the call (e.g. video).
170         *
171         * <P>Type: INTEGER (int)</P>
172         */
173        public static final String FEATURES = "features";
174
175        /** Call had video. */
176        public static final int FEATURES_VIDEO = 0x1;
177
178        /**
179         * The phone number as the user entered it.
180         * <P>Type: TEXT</P>
181         */
182        public static final String NUMBER = "number";
183
184        /**
185         * The number presenting rules set by the network.
186         *
187         * <p>
188         * Allowed values:
189         * <ul>
190         * <li>{@link #PRESENTATION_ALLOWED}</li>
191         * <li>{@link #PRESENTATION_RESTRICTED}</li>
192         * <li>{@link #PRESENTATION_UNKNOWN}</li>
193         * <li>{@link #PRESENTATION_PAYPHONE}</li>
194         * </ul>
195         * </p>
196         *
197         * <P>Type: INTEGER</P>
198         */
199        public static final String NUMBER_PRESENTATION = "presentation";
200
201        /** Number is allowed to display for caller id. */
202        public static final int PRESENTATION_ALLOWED = 1;
203        /** Number is blocked by user. */
204        public static final int PRESENTATION_RESTRICTED = 2;
205        /** Number is not specified or unknown by network. */
206        public static final int PRESENTATION_UNKNOWN = 3;
207        /** Number is a pay phone. */
208        public static final int PRESENTATION_PAYPHONE = 4;
209
210        /**
211         * The ISO 3166-1 two letters country code of the country where the
212         * user received or made the call.
213         * <P>
214         * Type: TEXT
215         * </P>
216         */
217        public static final String COUNTRY_ISO = "countryiso";
218
219        /**
220         * The date the call occured, in milliseconds since the epoch
221         * <P>Type: INTEGER (long)</P>
222         */
223        public static final String DATE = "date";
224
225        /**
226         * The duration of the call in seconds
227         * <P>Type: INTEGER (long)</P>
228         */
229        public static final String DURATION = "duration";
230
231        /**
232         * The data usage of the call in bytes.
233         * <P>Type: INTEGER (long)</P>
234         */
235        public static final String DATA_USAGE = "data_usage";
236
237        /**
238         * Whether or not the call has been acknowledged
239         * <P>Type: INTEGER (boolean)</P>
240         */
241        public static final String NEW = "new";
242
243        /**
244         * The cached name associated with the phone number, if it exists.
245         * This value is not guaranteed to be current, if the contact information
246         * associated with this number has changed.
247         * <P>Type: TEXT</P>
248         */
249        public static final String CACHED_NAME = "name";
250
251        /**
252         * The cached number type (Home, Work, etc) associated with the
253         * phone number, if it exists.
254         * This value is not guaranteed to be current, if the contact information
255         * associated with this number has changed.
256         * <P>Type: INTEGER</P>
257         */
258        public static final String CACHED_NUMBER_TYPE = "numbertype";
259
260        /**
261         * The cached number label, for a custom number type, associated with the
262         * phone number, if it exists.
263         * This value is not guaranteed to be current, if the contact information
264         * associated with this number has changed.
265         * <P>Type: TEXT</P>
266         */
267        public static final String CACHED_NUMBER_LABEL = "numberlabel";
268
269        /**
270         * URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}.
271         * <P>Type: TEXT</P>
272         */
273        public static final String VOICEMAIL_URI = "voicemail_uri";
274
275        /**
276         * Transcription of the call or voicemail entry. This will only be populated for call log
277         * entries of type {@link #VOICEMAIL_TYPE} that have valid transcriptions.
278         */
279        public static final String TRANSCRIPTION = "transcription";
280
281        /**
282         * Whether this item has been read or otherwise consumed by the user.
283         * <p>
284         * Unlike the {@link #NEW} field, which requires the user to have acknowledged the
285         * existence of the entry, this implies the user has interacted with the entry.
286         * <P>Type: INTEGER (boolean)</P>
287         */
288        public static final String IS_READ = "is_read";
289
290        /**
291         * A geocoded location for the number associated with this call.
292         * <p>
293         * The string represents a city, state, or country associated with the number.
294         * <P>Type: TEXT</P>
295         */
296        public static final String GEOCODED_LOCATION = "geocoded_location";
297
298        /**
299         * The cached URI to look up the contact associated with the phone number, if it exists.
300         * This value may not be current if the contact information associated with this number
301         * has changed.
302         * <P>Type: TEXT</P>
303         */
304        public static final String CACHED_LOOKUP_URI = "lookup_uri";
305
306        /**
307         * The cached phone number of the contact which matches this entry, if it exists.
308         * This value may not be current if the contact information associated with this number
309         * has changed.
310         * <P>Type: TEXT</P>
311         */
312        public static final String CACHED_MATCHED_NUMBER = "matched_number";
313
314        /**
315         * The cached normalized(E164) version of the phone number, if it exists.
316         * This value may not be current if the contact information associated with this number
317         * has changed.
318         * <P>Type: TEXT</P>
319         */
320        public static final String CACHED_NORMALIZED_NUMBER = "normalized_number";
321
322        /**
323         * The cached photo id of the picture associated with the phone number, if it exists.
324         * This value may not be current if the contact information associated with this number
325         * has changed.
326         * <P>Type: INTEGER (long)</P>
327         */
328        public static final String CACHED_PHOTO_ID = "photo_id";
329
330        /**
331         * The cached photo URI of the picture associated with the phone number, if it exists.
332         * This value may not be current if the contact information associated with this number
333         * has changed.
334         * <P>Type: TEXT (URI)</P>
335         */
336        public static final String CACHED_PHOTO_URI = "photo_uri";
337
338        /**
339         * The cached phone number, formatted with formatting rules based on the country the
340         * user was in when the call was made or received.
341         * This value is not guaranteed to be present, and may not be current if the contact
342         * information associated with this number
343         * has changed.
344         * <P>Type: TEXT</P>
345         */
346        public static final String CACHED_FORMATTED_NUMBER = "formatted_number";
347
348        // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming
349        // that was encoded into call log databases.
350
351        /**
352         * The component name of the account used to place or receive the call; in string form.
353         * <P>Type: TEXT</P>
354         */
355        public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
356
357        /**
358         * The identifier for the account used to place or receive the call.
359         * <P>Type: TEXT</P>
360         */
361        public static final String PHONE_ACCOUNT_ID = "subscription_id";
362
363        /**
364         * Indicates that the entry will be hidden from all queries until the associated
365         * {@link android.telecom.PhoneAccount} is registered with the system.
366         * <P>Type: INTEGER</P>
367         *
368         * @Deprecated
369         * @hide
370         */
371        public static final String PHONE_ACCOUNT_HIDDEN = "phone_account_hidden";
372
373        /**
374         * The address associated with the account used to place or receive the call; in string
375         * form. For SIM-based calls, this is the user's own phone number.
376         * <P>Type: TEXT</P>
377         *
378         * @hide
379         */
380        public static final String PHONE_ACCOUNT_ADDRESS = "phone_account_address";
381
382        /**
383         * The subscription ID used to place this call.  This is no longer used and has been
384         * replaced with PHONE_ACCOUNT_COMPONENT_NAME/PHONE_ACCOUNT_ID.
385         * For ContactsProvider internal use only.
386         * <P>Type: INTEGER</P>
387         *
388         * @Deprecated
389         * @hide
390         */
391        public static final String SUB_ID = "sub_id";
392
393        /**
394         * If a successful call is made that is longer than this duration, update the phone number
395         * in the ContactsProvider with the normalized version of the number, based on the user's
396         * current country code.
397         */
398        private static final int MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS = 1000 * 10;
399
400        /**
401         * Adds a call to the call log.
402         *
403         * @param ci the CallerInfo object to get the target contact from.  Can be null
404         * if the contact is unknown.
405         * @param context the context used to get the ContentResolver
406         * @param number the phone number to be added to the calls db
407         * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which
408         *        is set by the network and denotes the number presenting rules for
409         *        "allowed", "payphone", "restricted" or "unknown"
410         * @param callType enumerated values for "incoming", "outgoing", or "missed"
411         * @param features features of the call (e.g. Video).
412         * @param accountHandle The accountHandle object identifying the provider of the call
413         * @param start time stamp for the call in milliseconds
414         * @param duration call duration in seconds
415         * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for
416         *                  the call.
417         * @result The URI of the call log entry belonging to the user that made or received this
418         *        call.
419         * {@hide}
420         */
421        public static Uri addCall(CallerInfo ci, Context context, String number,
422                int presentation, int callType, int features, PhoneAccountHandle accountHandle,
423                long start, int duration, Long dataUsage) {
424            return addCall(ci, context, number, presentation, callType, features, accountHandle,
425                    start, duration, dataUsage, false);
426        }
427
428
429        /**
430         * Adds a call to the call log.
431         *
432         * @param ci the CallerInfo object to get the target contact from.  Can be null
433         * if the contact is unknown.
434         * @param context the context used to get the ContentResolver
435         * @param number the phone number to be added to the calls db
436         * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which
437         *        is set by the network and denotes the number presenting rules for
438         *        "allowed", "payphone", "restricted" or "unknown"
439         * @param callType enumerated values for "incoming", "outgoing", or "missed"
440         * @param features features of the call (e.g. Video).
441         * @param accountHandle The accountHandle object identifying the provider of the call
442         * @param start time stamp for the call in milliseconds
443         * @param duration call duration in seconds
444         * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for
445         *                  the call.
446         * @param addForAllUsers If true, the call is added to the call log of all currently
447         *        running users. The caller must have the MANAGE_USERS permission if this is true.
448         *
449         * @result The URI of the call log entry belonging to the user that made or received this
450         *        call.
451         * {@hide}
452         */
453        public static Uri addCall(CallerInfo ci, Context context, String number,
454                int presentation, int callType, int features, PhoneAccountHandle accountHandle,
455                long start, int duration, Long dataUsage, boolean addForAllUsers) {
456            final ContentResolver resolver = context.getContentResolver();
457            int numberPresentation = PRESENTATION_ALLOWED;
458
459            TelecomManager tm = null;
460            try {
461                tm = TelecomManager.from(context);
462            } catch (UnsupportedOperationException e) {}
463
464            String accountAddress = null;
465            if (tm != null && accountHandle != null) {
466                PhoneAccount account = tm.getPhoneAccount(accountHandle);
467                if (account != null) {
468                    accountAddress = account.getSubscriptionAddress().getSchemeSpecificPart();
469                }
470            }
471
472            // Remap network specified number presentation types
473            // PhoneConstants.PRESENTATION_xxx to calllog number presentation types
474            // Calls.PRESENTATION_xxx, in order to insulate the persistent calllog
475            // from any future radio changes.
476            // If the number field is empty set the presentation type to Unknown.
477            if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
478                numberPresentation = PRESENTATION_RESTRICTED;
479            } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
480                numberPresentation = PRESENTATION_PAYPHONE;
481            } else if (TextUtils.isEmpty(number)
482                    || presentation == PhoneConstants.PRESENTATION_UNKNOWN) {
483                numberPresentation = PRESENTATION_UNKNOWN;
484            }
485            if (numberPresentation != PRESENTATION_ALLOWED) {
486                number = "";
487                if (ci != null) {
488                    ci.name = "";
489                }
490            }
491
492            // accountHandle information
493            String accountComponentString = null;
494            String accountId = null;
495            if (accountHandle != null) {
496                accountComponentString = accountHandle.getComponentName().flattenToString();
497                accountId = accountHandle.getId();
498            }
499
500            ContentValues values = new ContentValues(6);
501
502            values.put(NUMBER, number);
503            values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
504            values.put(TYPE, Integer.valueOf(callType));
505            values.put(FEATURES, features);
506            values.put(DATE, Long.valueOf(start));
507            values.put(DURATION, Long.valueOf(duration));
508            if (dataUsage != null) {
509                values.put(DATA_USAGE, dataUsage);
510            }
511            values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
512            values.put(PHONE_ACCOUNT_ID, accountId);
513            values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
514            values.put(NEW, Integer.valueOf(1));
515
516            if (callType == MISSED_TYPE) {
517                values.put(IS_READ, Integer.valueOf(0));
518            }
519            if (ci != null) {
520                values.put(CACHED_NAME, ci.name);
521                values.put(CACHED_NUMBER_TYPE, ci.numberType);
522                values.put(CACHED_NUMBER_LABEL, ci.numberLabel);
523            }
524
525            if ((ci != null) && (ci.contactIdOrZero > 0)) {
526                // Update usage information for the number associated with the contact ID.
527                // We need to use both the number and the ID for obtaining a data ID since other
528                // contacts may have the same number.
529
530                final Cursor cursor;
531
532                // We should prefer normalized one (probably coming from
533                // Phone.NORMALIZED_NUMBER column) first. If it isn't available try others.
534                if (ci.normalizedNumber != null) {
535                    final String normalizedPhoneNumber = ci.normalizedNumber;
536                    cursor = resolver.query(Phone.CONTENT_URI,
537                            new String[] { Phone._ID },
538                            Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?",
539                            new String[] { String.valueOf(ci.contactIdOrZero),
540                                    normalizedPhoneNumber},
541                            null);
542                } else {
543                    final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number;
544                    cursor = resolver.query(
545                            Uri.withAppendedPath(Callable.CONTENT_FILTER_URI,
546                                    Uri.encode(phoneNumber)),
547                            new String[] { Phone._ID },
548                            Phone.CONTACT_ID + " =?",
549                            new String[] { String.valueOf(ci.contactIdOrZero) },
550                            null);
551                }
552
553                if (cursor != null) {
554                    try {
555                        if (cursor.getCount() > 0 && cursor.moveToFirst()) {
556                            final String dataId = cursor.getString(0);
557                            updateDataUsageStatForData(resolver, dataId);
558                            if (duration >= MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS
559                                    && callType == Calls.OUTGOING_TYPE
560                                    && TextUtils.isEmpty(ci.normalizedNumber)) {
561                                updateNormalizedNumber(context, resolver, dataId, number);
562                            }
563                        }
564                    } finally {
565                        cursor.close();
566                    }
567                }
568            }
569
570            Uri result = null;
571
572            if (addForAllUsers) {
573                // Insert the entry for all currently running users, in order to trigger any
574                // ContentObservers currently set on the call log.
575                final UserManager userManager = (UserManager) context.getSystemService(
576                        Context.USER_SERVICE);
577                List<UserInfo> users = userManager.getUsers(true);
578                final int currentUserId = userManager.getUserHandle();
579                final int count = users.size();
580                for (int i = 0; i < count; i++) {
581                    final UserInfo user = users.get(i);
582                    final UserHandle userHandle = user.getUserHandle();
583                    if (userManager.isUserRunning(userHandle)
584                            && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
585                                    userHandle)
586                            && !user.isManagedProfile()) {
587                        Uri uri = addEntryAndRemoveExpiredEntries(context,
588                                ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values);
589                        if (user.id == currentUserId) {
590                            result = uri;
591                        }
592                    }
593                }
594            } else {
595                result = addEntryAndRemoveExpiredEntries(context, CONTENT_URI, values);
596            }
597
598            return result;
599        }
600
601        /**
602         * Query the call log database for the last dialed number.
603         * @param context Used to get the content resolver.
604         * @return The last phone number dialed (outgoing) or an empty
605         * string if none exist yet.
606         */
607        public static String getLastOutgoingCall(Context context) {
608            final ContentResolver resolver = context.getContentResolver();
609            Cursor c = null;
610            try {
611                c = resolver.query(
612                    CONTENT_URI,
613                    new String[] {NUMBER},
614                    TYPE + " = " + OUTGOING_TYPE,
615                    null,
616                    DEFAULT_SORT_ORDER + " LIMIT 1");
617                if (c == null || !c.moveToFirst()) {
618                    return "";
619                }
620                return c.getString(0);
621            } finally {
622                if (c != null) c.close();
623            }
624        }
625
626        private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri,
627                ContentValues values) {
628            final ContentResolver resolver = context.getContentResolver();
629            Uri result = resolver.insert(uri, values);
630            resolver.delete(uri, "_id IN " +
631                    "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
632                    + " LIMIT -1 OFFSET 500)", null);
633            return result;
634        }
635
636        private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) {
637            final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
638                    .appendPath(dataId)
639                    .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
640                                DataUsageFeedback.USAGE_TYPE_CALL)
641                    .build();
642            resolver.update(feedbackUri, new ContentValues(), null, null);
643        }
644
645        /*
646         * Update the normalized phone number for the given dataId in the ContactsProvider, based
647         * on the user's current country.
648         */
649        private static void updateNormalizedNumber(Context context, ContentResolver resolver,
650                String dataId, String number) {
651            if (TextUtils.isEmpty(number) || TextUtils.isEmpty(dataId)) {
652                return;
653            }
654            final String countryIso = getCurrentCountryIso(context);
655            if (TextUtils.isEmpty(countryIso)) {
656                return;
657            }
658            final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number,
659                    getCurrentCountryIso(context));
660            if (TextUtils.isEmpty(normalizedNumber)) {
661                return;
662            }
663            final ContentValues values = new ContentValues();
664            values.put(Phone.NORMALIZED_NUMBER, normalizedNumber);
665            resolver.update(Data.CONTENT_URI, values, Data._ID + "=?", new String[] {dataId});
666        }
667
668        private static String getCurrentCountryIso(Context context) {
669            String countryIso = null;
670            final CountryDetector detector = (CountryDetector) context.getSystemService(
671                    Context.COUNTRY_DETECTOR);
672            if (detector != null) {
673                final Country country = detector.detectCountry();
674                if (country != null) {
675                    countryIso = country.getCountryIso();
676                }
677            }
678            return countryIso;
679        }
680    }
681}
682