/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.provider;
import android.accounts.Account;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.CursorEntityIterator;
import android.content.Entity;
import android.content.EntityIterator;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.graphics.Rect;
import android.net.Uri;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.View;
import android.widget.Toast;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* <p>
* The contract between the contacts provider and applications. Contains
* definitions for the supported URIs and columns. These APIs supersede
* {@link Contacts}.
* </p>
* <h3>Overview</h3>
* <p>
* ContactsContract defines an extensible database of contact-related
* information. Contact information is stored in a three-tier data model:
* </p>
* <ul>
* <li>
* A row in the {@link Data} table can store any kind of personal data, such
* as a phone number or email addresses. The set of data kinds that can be
* stored in this table is open-ended. There is a predefined set of common
* kinds, but any application can add its own data kinds.
* </li>
* <li>
* A row in the {@link RawContacts} table represents a set of data describing a
* person and associated with a single account (for example, one of the user's
* Gmail accounts).
* </li>
* <li>
* A row in the {@link Contacts} table represents an aggregate of one or more
* RawContacts presumably describing the same person. When data in or associated with
* the RawContacts table is changed, the affected aggregate contacts are updated as
* necessary.
* </li>
* </ul>
* <p>
* Other tables include:
* </p>
* <ul>
* <li>
* {@link Groups}, which contains information about raw contact groups
* such as Gmail contact groups. The
* current API does not support the notion of groups spanning multiple accounts.
* </li>
* <li>
* {@link StatusUpdates}, which contains social status updates including IM
* availability.
* </li>
* <li>
* {@link AggregationExceptions}, which is used for manual aggregation and
* disaggregation of raw contacts
* </li>
* <li>
* {@link Settings}, which contains visibility and sync settings for accounts
* and groups.
* </li>
* <li>
* {@link SyncState}, which contains free-form data maintained on behalf of sync
* adapters
* </li>
* <li>
* {@link PhoneLookup}, which is used for quick caller-ID lookup</li>
* </ul>
*/
@SuppressWarnings("unused")
public final class ContactsContract {
/** The authority for the contacts provider */
public static final String AUTHORITY = "com.android.contacts";
/** A content:// style uri to the authority for the contacts provider */
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
/**
* An optional URI parameter for insert, update, or delete queries
* that allows the caller
* to specify that it is a sync adapter. The default value is false. If true
* {@link RawContacts#DIRTY} is not automatically set and the
* "syncToNetwork" parameter is set to false when calling
* {@link
* ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
* This prevents an unnecessary extra synchronization, see the discussion of
* the delete operation in {@link RawContacts}.
*/
public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
/**
* Query parameter that should be used by the client to access a specific
* {@link Directory}. The parameter value should be the _ID of the corresponding
* directory, e.g.
* {@code content://com.android.contacts/data/emails/filter/acme?directory=3}
*/
public static final String DIRECTORY_PARAM_KEY = "directory";
/**
* A query parameter that limits the number of results returned. The
* parameter value should be an integer.
*/
public static final String LIMIT_PARAM_KEY = "limit";
/**
* A query parameter specifing a primary account. This parameter should be used with
* {@link #PRIMARY_ACCOUNT_TYPE}. The contacts provider handling a query may rely on
* this information to optimize its query results.
*
* For example, in an email composition screen, its implementation can specify an account when
* obtaining possible recipients, letting the provider know which account is selected during
* the composition. The provider may use the "primary account" information to optimize
* the search result.
*/
public static final String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
/**
* A query parameter specifing a primary account. This parameter should be used with
* {@link #PRIMARY_ACCOUNT_NAME}. See the doc in {@link #PRIMARY_ACCOUNT_NAME}.
*/
public static final String PRIMARY_ACCOUNT_TYPE = "type_for_primary_account";
/**
* A boolean parameter for {@link Contacts#CONTENT_STREQUENT_URI} and
* {@link Contacts#CONTENT_STREQUENT_FILTER_URI}, which requires the ContactsProvider to
* return only phone-related results. For example, frequently contacted person list should
* include persons contacted via phone (not email, sms, etc.)
*/
public static final String STREQUENT_PHONE_ONLY = "strequent_phone_only";
/**
* A key to a boolean in the "extras" bundle of the cursor.
* The boolean indicates that the provider did not create a snippet and that the client asking
* for the snippet should do it (true means the snippeting was deferred to the client).
*
* @see SearchSnippets
*/
public static final String DEFERRED_SNIPPETING = "deferred_snippeting";
/**
* Key to retrieve the original deferred snippeting from the cursor on the client side.
*
* @see SearchSnippets
* @see #DEFERRED_SNIPPETING
*/
public static final String DEFERRED_SNIPPETING_QUERY = "deferred_snippeting_query";
/**
* A boolean parameter for {@link CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI},
* {@link CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI}, and
* {@link CommonDataKinds.StructuredPostal#CONTENT_URI StructuredPostal.CONTENT_URI}.
* This enables a content provider to remove duplicate entries in results.
*/
public static final String REMOVE_DUPLICATE_ENTRIES = "remove_duplicate_entries";
/**
* <p>
* API for obtaining a pre-authorized version of a URI that normally requires special
* permission (beyond READ_CONTACTS) to read. The caller obtaining the pre-authorized URI
* must already have the necessary permissions to access the URI; otherwise a
* {@link SecurityException} will be thrown.
* </p>
* <p>
* The authorized URI returned in the bundle contains an expiring token that allows the
* caller to execute the query without having the special permissions that would normally
* be required.
* </p>
* <p>
* This API does not access disk, and should be safe to invoke from the UI thread.
* </p>
* <p>
* Example usage:
* <pre>
* Uri profileUri = ContactsContract.Profile.CONTENT_VCARD_URI;
* Bundle uriBundle = new Bundle();
* uriBundle.putParcelable(ContactsContract.Authorization.KEY_URI_TO_AUTHORIZE, uri);
* Bundle authResponse = getContext().getContentResolver().call(
* ContactsContract.AUTHORITY_URI,
* ContactsContract.Authorization.AUTHORIZATION_METHOD,
* null, // String arg, not used.
* uriBundle);
* if (authResponse != null) {
* Uri preauthorizedProfileUri = (Uri) authResponse.getParcelable(
* ContactsContract.Authorization.KEY_AUTHORIZED_URI);
* // This pre-authorized URI can be queried by a caller without READ_PROFILE
* // permission.
* }
* </pre>
* </p>
* @hide
*/
public static final class Authorization {
/**
* The method to invoke to create a pre-authorized URI out of the input argument.
*/
public static final String AUTHORIZATION_METHOD = "authorize";
/**
* The key to set in the outbound Bundle with the URI that should be authorized.
*/
public static final String KEY_URI_TO_AUTHORIZE = "uri_to_authorize";
/**
* The key to retrieve from the returned Bundle to obtain the pre-authorized URI.
*/
public static final String KEY_AUTHORIZED_URI = "authorized_uri";
}
/**
* A Directory represents a contacts corpus, e.g. Local contacts,
* Google Apps Global Address List or Corporate Global Address List.
* <p>
* A Directory is implemented as a content provider with its unique authority and
* the same API as the main Contacts Provider. However, there is no expectation that
* every directory provider will implement this Contract in its entirety. If a
* directory provider does not have an implementation for a specific request, it
* should throw an UnsupportedOperationException.
* </p>
* <p>
* The most important use case for Directories is search. A Directory provider is
* expected to support at least {@link ContactsContract.Contacts#CONTENT_FILTER_URI
* Contacts.CONTENT_FILTER_URI}. If a Directory provider wants to participate
* in email and phone lookup functionalities, it should also implement
* {@link CommonDataKinds.Email#CONTENT_FILTER_URI CommonDataKinds.Email.CONTENT_FILTER_URI}
* and
* {@link CommonDataKinds.Phone#CONTENT_FILTER_URI CommonDataKinds.Phone.CONTENT_FILTER_URI}.
* </p>
* <p>
* A directory provider should return NULL for every projection field it does not
* recognize, rather than throwing an exception. This way it will not be broken
* if ContactsContract is extended with new fields in the future.
* </p>
* <p>
* The client interacts with a directory via Contacts Provider by supplying an
* optional {@code directory=} query parameter.
* <p>
* <p>
* When the Contacts Provider receives the request, it transforms the URI and forwards
* the request to the corresponding directory content provider.
* The URI is transformed in the following fashion:
* <ul>
* <li>The URI authority is replaced with the corresponding {@link #DIRECTORY_AUTHORITY}.</li>
* <li>The {@code accountName=} and {@code accountType=} parameters are added or
* replaced using the corresponding {@link #ACCOUNT_TYPE} and {@link #ACCOUNT_NAME} values.</li>
* </ul>
* </p>
* <p>
* Clients should send directory requests to Contacts Provider and let it
* forward them to the respective providers rather than constructing
* directory provider URIs by themselves. This level of indirection allows
* Contacts Provider to implement additional system-level features and
* optimizations. Access to Contacts Provider is protected by the
* READ_CONTACTS permission, but access to the directory provider is protected by
* BIND_DIRECTORY_SEARCH. This permission was introduced at the API level 17, for previous
* platform versions the provider should perform the following check to make sure the call
* is coming from the ContactsProvider:
* <pre>
* private boolean isCallerAllowed() {
* PackageManager pm = getContext().getPackageManager();
* for (String packageName: pm.getPackagesForUid(Binder.getCallingUid())) {
* if (packageName.equals("com.android.providers.contacts")) {
* return true;
* }
* }
* return false;
* }
* </pre>
* </p>
* <p>
* The Directory table is read-only and is maintained by the Contacts Provider
* automatically.
* </p>
* <p>It always has at least these two rows:
* <ul>
* <li>
* The local directory. It has {@link Directory#_ID Directory._ID} =
* {@link Directory#DEFAULT Directory.DEFAULT}. This directory can be used to access locally
* stored contacts. The same can be achieved by omitting the {@code directory=}
* parameter altogether.
* </li>
* <li>
* The local invisible contacts. The corresponding directory ID is
* {@link Directory#LOCAL_INVISIBLE Directory.LOCAL_INVISIBLE}.
* </li>
* </ul>
* </p>
* <p>Custom Directories are discovered by the Contacts Provider following this procedure:
* <ul>
* <li>It finds all installed content providers with meta data identifying them
* as directory providers in AndroidManifest.xml:
* <code>
* <meta-data android:name="android.content.ContactDirectory"
* android:value="true" />
* </code>
* <p>
* This tag should be placed inside the corresponding content provider declaration.
* </p>
* </li>
* <li>
* Then Contacts Provider sends a {@link Directory#CONTENT_URI Directory.CONTENT_URI}
* query to each of the directory authorities. A directory provider must implement
* this query and return a list of directories. Each directory returned by
* the provider must have a unique combination for the {@link #ACCOUNT_NAME} and
* {@link #ACCOUNT_TYPE} columns (nulls are allowed). Since directory IDs are assigned
* automatically, the _ID field will not be part of the query projection.
* </li>
* <li>Contacts Provider compiles directory lists received from all directory
* providers into one, assigns each individual directory a globally unique ID and
* stores all directory records in the Directory table.
* </li>
* </ul>
* </p>
* <p>Contacts Provider automatically interrogates newly installed or replaced packages.
* Thus simply installing a package containing a directory provider is sufficient
* to have that provider registered. A package supplying a directory provider does
* not have to contain launchable activities.
* </p>
* <p>
* Every row in the Directory table is automatically associated with the corresponding package
* (apk). If the package is later uninstalled, all corresponding directory rows
* are automatically removed from the Contacts Provider.
* </p>
* <p>
* When the list of directories handled by a directory provider changes
* (for instance when the user adds a new Directory account), the directory provider
* should call {@link #notifyDirectoryChange} to notify the Contacts Provider of the change.
* In response, the Contacts Provider will requery the directory provider to obtain the
* new list of directories.
* </p>
* <p>
* A directory row can be optionally associated with an existing account
* (see {@link android.accounts.AccountManager}). If the account is later removed,
* the corresponding directory rows are automatically removed from the Contacts Provider.
* </p>
*/
public static final class Directory implements BaseColumns {
/**
* Not instantiable.
*/
private Directory() {
}
/**
* The content:// style URI for this table. Requests to this URI can be
* performed on the UI thread because they are always unblocking.
*/
public static final Uri CONTENT_URI =
Uri.withAppendedPath(AUTHORITY_URI, "directories");
/**
* The MIME-type of {@link #CONTENT_URI} providing a directory of
* contact directories.
*/
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/contact_directories";
/**
* The MIME type of a {@link #CONTENT_URI} item.
*/
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/contact_directory";
/**
* _ID of the default directory, which represents locally stored contacts.
*/
public static final long DEFAULT = 0;
/**
* _ID of the directory that represents locally stored invisible contacts.
*/
public static final long LOCAL_INVISIBLE = 1;
/**
* The name of the package that owns this directory. Contacts Provider
* fill it in with the name of the package containing the directory provider.
* If the package is later uninstalled, the directories it owns are
* automatically removed from this table.
*
* <p>TYPE: TEXT</p>
*/
public static final String PACKAGE_NAME = "packageName";
/**
* The type of directory captured as a resource ID in the context of the
* package {@link #PACKAGE_NAME}, e.g. "Corporate Directory"
*
* <p>TYPE: INTEGER</p>
*/
public static final String TYPE_RESOURCE_ID = "typeResourceId";
/**
* An optional name that can be used in the UI to represent this directory,
* e.g. "Acme Corp"
* <p>TYPE: text</p>
*/
public static final String DISPLAY_NAME = "displayName";
/**
* <p>
* The authority of the Directory Provider. Contacts Provider will
* use this authority to forward requests to the directory provider.
* A directory provider can leave this column empty - Contacts Provider will fill it in.
* </p>
* <p>
* Clients of this API should not send requests directly to this authority.
* All directory requests must be routed through Contacts Provider.
* </p>
*
* <p>TYPE: text</p>
*/
public static final String DIRECTORY_AUTHORITY = "authority";
/**
* The account type which this directory is associated.
*
* <p>TYPE: text</p>
*/
public static final String ACCOUNT_TYPE = "accountType";
/**
* The account with which this directory is associated. If the account is later
* removed, the directories it owns are automatically removed from this table.
*
* <p>TYPE: text</p>
*/
public static final String ACCOUNT_NAME = "accountName";
/**
* One of {@link #EXPORT_SUPPORT_NONE}, {@link #EXPORT_SUPPORT_ANY_ACCOUNT},
* {@link #EXPORT_SUPPORT_SAME_ACCOUNT_ONLY}. This is the expectation the
* directory has for data exported from it. Clients must obey this setting.
*/
public static final String EXPORT_SUPPORT = "exportSupport";
/**
* An {@link #EXPORT_SUPPORT} setting that indicates that the directory
* does not allow any data to be copied out of it.
*/
public static final int EXPORT_SUPPORT_NONE = 0;
/**
* An {@link #EXPORT_SUPPORT} setting that indicates that the directory
* allow its data copied only to the account specified by
* {@link #ACCOUNT_TYPE}/{@link #ACCOUNT_NAME}.
*/
public static final int EXPORT_SUPPORT_SAME_ACCOUNT_ONLY = 1;
/**
* An {@link #EXPORT_SUPPORT} setting that indicates that the directory
* allow its data copied to any contacts account.
*/
public static final int EXPORT_SUPPORT_ANY_ACCOUNT = 2;
/**
* One of {@link #SHORTCUT_SUPPORT_NONE}, {@link #SHORTCUT_SUPPORT_DATA_ITEMS_ONLY},
* {@link #SHORTCUT_SUPPORT_FULL}. This is the expectation the directory
* has for shortcuts created for its elements. Clients must obey this setting.
*/
public static final String SHORTCUT_SUPPORT = "shortcutSupport";
/**
* An {@link #SHORTCUT_SUPPORT} setting that indicates that the directory
* does not allow any shortcuts created for its contacts.
*/
public static final int SHORTCUT_SUPPORT_NONE = 0;
/**
* An {@link #SHORTCUT_SUPPORT} setting that indicates that the directory
* allow creation of shortcuts for data items like email, phone or postal address,
* but not the entire contact.
*/
public static final int SHORTCUT_SUPPORT_DATA_ITEMS_ONLY = 1;
/**
* An {@link #SHORTCUT_SUPPORT} setting that indicates that the directory
* allow creation of shortcuts for contact as well as their constituent elements.
*/
public static final int SHORTCUT_SUPPORT_FULL = 2;
/**
* One of {@link #PHOTO_SUPPORT_NONE}, {@link #PHOTO_SUPPORT_THUMBNAIL_ONLY},
* {@link #PHOTO_SUPPORT_FULL}. This is a feature flag indicating the extent
* to which the directory supports contact photos.
*/
public static final String PHOTO_SUPPORT = "photoSupport";
/**
* An {@link #PHOTO_SUPPORT} setting that indicates that the directory
* does not provide any photos.
*/
public static final int PHOTO_SUPPORT_NONE = 0;
/**
* An {@link #PHOTO_SUPPORT} setting that indicates that the directory
* can only produce small size thumbnails of contact photos.
*/
public static final int PHOTO_SUPPORT_THUMBNAIL_ONLY = 1;
/**
* An {@link #PHOTO_SUPPORT} setting that indicates that the directory
* has full-size contact photos, but cannot provide scaled thumbnails.
*/
public static final int PHOTO_SUPPORT_FULL_SIZE_ONLY = 2;
/**
* An {@link #PHOTO_SUPPORT} setting that indicates that the directory
* can produce thumbnails as well as full-size contact photos.
*/
public static final int PHOTO_SUPPORT_FULL = 3;
/**
* Notifies the system of a change in the list of directories handled by
* a particular directory provider. The Contacts provider will turn around
* and send a query to the directory provider for the full list of directories,
* which will replace the previous list.
*/
public static void notifyDirectoryChange(ContentResolver resolver) {
// This is done to trigger a query by Contacts Provider back to the directory provider.
// No data needs to be sent back, because the provider can infer the calling
// package from binder.
ContentValues contentValues = new ContentValues();
resolver.update(Directory.CONTENT_URI, contentValues, null, null);
}
}
/**
* @hide should be removed when users are updated to refer to SyncState
* @deprecated use SyncState instead
*/
@Deprecated
public interface SyncStateColumns extends SyncStateContract.Columns {
}
/**
* A table provided for sync adapters to use for storing private sync state data for contacts.
*
* @see SyncStateContract
*/
public static final class SyncState implements SyncStateContract.Columns {
/**
* This utility class cannot be instantiated
*/
private SyncState() {}
public static final String CONTENT_DIRECTORY =
SyncStateContract.Constants.CONTENT_DIRECTORY;
/**
* The content:// style URI for this table
*/
public static final Uri CONTENT_URI =
Uri.withAppendedPath(AUTHORITY_URI, CONTENT_DIRECTORY);
/**
* @see android.provider.SyncStateContract.Helpers#get
*/
public static byte[] get(ContentProviderClient provider, Account account)
throws RemoteException {
return SyncStateContract.Helpers.get(provider, CONTENT_URI, account);
}
/**
* @see android.provider.SyncStateContract.Helpers#get
*/
public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Account account)
throws RemoteException {
return SyncStateContract.Helpers.getWithUri(provider, CONTENT_URI, account);
}
/**
* @see android.provider.SyncStateContract.Helpers#set
*/
public static void set(ContentProviderClient provider, Account account, byte[] data)
throws RemoteException {
SyncStateContract.Helpers.set(provider, CONTENT_URI, account, data);
}
/**
* @see android.provider.SyncStateContract.Helpers#newSetOperation
*/
public static ContentProviderOperation newSetOperation(Account account, byte[] data) {
return SyncStateContract.Helpers.newSetOperation(CONTENT_URI, account, data);
}
}
/**
* A table provided for sync adapters to use for storing private sync state data for the
* user's personal profile.
*
* @see SyncStateContract
*/
public static final class ProfileSyncState implements SyncStateContract.Columns {
/**
* This utility class cannot be instantiated
*/
private ProfileSyncState() {}
public static final String CONTENT_DIRECTORY =
SyncStateContract.Constants.CONTENT_DIRECTORY;
/**
* The content:// style URI for this table
*/
public static final Uri CONTENT_URI =
Uri.withAppendedPath(Profile.CONTENT_URI, CONTENT_DIRECTORY);
/**
* @see android.provider.SyncStateContract.Helpers#get
*/
public static byte[] get(ContentProviderClient provider, Account account)
throws RemoteException {
return SyncStateContract.Helpers.get(provider, CONTENT_URI, account);
}
/**
* @see android.provider.SyncStateContract.Helpers#get
*/
public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Account account)
throws RemoteException {
return SyncStateContract.Helpers.getWithUri(provider, CONTENT_URI, account);
}
/**
* @see android.provider.SyncStateContract.Helpers#set
*/
public static void set(ContentProviderClient provider, Account account, byte[] data)
throws RemoteException {
SyncStateContract.Helpers.set(provider, CONTENT_URI, account, data);
}
/**
* @see android.provider.SyncStateContract.Helpers#newSetOperation
*/
public static ContentProviderOperation newSetOperation(Account account, byte[] data) {
return SyncStateContract.Helpers.newSetOperation(CONTENT_URI, account, data);
}
}
/**
* Generic columns for use by sync adapters. The specific functions of
* these columns are private to the sync adapter. Other clients of the API
* should not attempt to either read or write this column.
*
* @see RawContacts
* @see Groups
*/
protected interface BaseSyncColumns {
/** Generic column for use by sync adapters. */
public static final String SYNC1 = "sync1";
/** Generic column for use by sync adapters. */
public static final String SYNC2 = "sync2";
/** Generic column for use by sync adapters. */
public static final String SYNC3 = "sync3";
/** Generic column for use by sync adapters. */
public static final String SYNC4 = "sync4";
}
/**
* Columns that appear when each row of a table belongs to a specific
* account, including sync information that an account may need.
*
* @see RawContacts
* @see Groups
*/
protected interface SyncColumns extends BaseSyncColumns {
/**
* The name of the account instance to which this row belongs, which when paired with
* {@link #ACCOUNT_TYPE} identifies a specific account.
* <P>Type: TEXT</P>
*/
public static final String ACCOUNT_NAME = "account_name";
/**
* The type of account to which this row belongs, which when paired with
* {@link #ACCOUNT_NAME} identifies a specific account.
* <P>Type: TEXT</P>
*/
public static final String ACCOUNT_TYPE = "account_type";
/**
* String that uniquely identifies this row to its source account.
* <P>Type: TEXT</P>
*/
public static final String SOURCE_ID = "sourceid";
/**
* Version number that is updated whenever this row or its related data
* changes.
* <P>Type: INTEGER</P>
*/
public static final String VERSION = "version";
/**
* Flag indicating that {@link #VERSION} has changed, and this row needs
* to be synchronized by its owning account.
* <P>Type: INTEGER (boolean)</P>
*/
public static final String DIRTY = "dirty";
}
/**
* Columns of {@link ContactsContract.Contacts} that track the user's
* preferences for, or interactions with, the contact.
*
* @see Contacts
* @see RawContacts
* @see ContactsContract.Data
* @see PhoneLookup
* @see ContactsContract.Contacts.AggregationSuggestions
*/
protected interface ContactOptionsColumns {
/**
* The number of times a contact has been contacted
* <P>Type: INTEGER</P>
*/
public static final String TIMES_CONTACTED = "times_contacted";
/**
* The last time a contact was contacted.
* <P>Type: INTEGER</P>
*/
public static final String LAST_TIME_CONTACTED = "last_time_contacted";
/**
* Is the contact starred?
* <P>Type: INTEGER (boolean)</P>
*/
public static final String STARRED = "starred";
/**
* The position at which the contact is pinned. If {@link PinnedPositions#UNPINNED},
* the contact is not pinned. Also see {@link PinnedPositions}.
* <P>Type: INTEGER </P>
*/
public static final String PINNED = "pinned";
/**
* URI for a custom ringtone associated with the contact. If null or missing,
* the default ringtone is used.
* <P>Type: TEXT (URI to the ringtone)</P>
*/
public static final String CUSTOM_RINGTONE = "custom_ringtone";
/**
* Whether the contact should always be sent to voicemail. If missing,
* defaults to false.
* <P>Type: INTEGER (0 for false, 1 for true)</P>
*/
public static final String SEND_TO_VOICEMAIL = "send_to_voicemail";
}
/**
* Columns of {@link ContactsContract.Contacts} that refer to intrinsic
* properties of the contact, as opposed to the user-specified options
* found in {@link ContactOptionsColumns}.
*
* @see Contacts
* @see ContactsContract.Data
* @see PhoneLookup
* @see ContactsContract.Contacts.AggregationSuggestions
*/
protected interface ContactsColumns {
/**
* The display name for the contact.
* <P>Type: TEXT</P>
*/
public static final String DISPLAY_NAME = ContactNameColumns.DISPLAY_NAME_PRIMARY;
/**
* Reference to the row in the RawContacts table holding the contact name.
* <P>Type: INTEGER REFERENCES raw_contacts(_id)</P>
*/
public static final String NAME_RAW_CONTACT_ID = "name_raw_contact_id";
/**
* Reference to the row in the data table holding the photo. A photo can
* be referred to either by ID (this field) or by URI (see {@link #PHOTO_THUMBNAIL_URI}
* and {@link #PHOTO_URI}).
* If PHOTO_ID is null, consult {@link #PHOTO_URI} or {@link #PHOTO_THUMBNAIL_URI},
* which is a more generic mechanism for referencing the contact photo, especially for
* contacts returned by non-local directories (see {@link Directory}).
*
* <P>Type: INTEGER REFERENCES data(_id)</P>
*/
public static final String PHOTO_ID = "photo_id";
/**
* Photo file ID of the full-size photo. If present, this will be used to populate
* {@link #PHOTO_URI}. The ID can also be used with
* {@link ContactsContract.DisplayPhoto#CONTENT_URI} to create a URI to the photo.
* If this is present, {@link #PHOTO_ID} is also guaranteed to be populated.
*
* <P>Type: INTEGER</P>
*/
public static final String PHOTO_FILE_ID = "photo_file_id";
/**
* A URI that can be used to retrieve the contact's full-size photo.
* If PHOTO_FILE_ID is not null, this will be populated with a URI based off
* {@link ContactsContract.DisplayPhoto#CONTENT_URI}. Otherwise, this will
* be populated with the same value as {@link #PHOTO_THUMBNAIL_URI}.
* A photo can be referred to either by a URI (this field) or by ID
* (see {@link #PHOTO_ID}). If either PHOTO_FILE_ID or PHOTO_ID is not null,
* PHOTO_URI and PHOTO_THUMBNAIL_URI shall not be null (but not necessarily
* vice versa). Thus using PHOTO_URI is a more robust method of retrieving
* contact photos.
*
* <P>Type: TEXT</P>
*/
public static final String PHOTO_URI = "photo_uri";
/**
* A URI that can be used to retrieve a thumbnail of the contact's photo.
* A photo can be referred to either by a URI (this field or {@link #PHOTO_URI})
* or by ID (see {@link #PHOTO_ID}). If PHOTO_ID is not null, PHOTO_URI and
* PHOTO_THUMBNAIL_URI shall not be null (but not necessarily vice versa).
* If the content provider does not differentiate between full-size photos
* and thumbnail photos, PHOTO_THUMBNAIL_URI and {@link #PHOTO_URI} can contain
* the same value, but either both shall be null or both not null.
*
* <P>Type: TEXT</P>
*/
public static final String PHOTO_THUMBNAIL_URI = "photo_thumb_uri";
/**
* Flag that reflects whether the contact exists inside the default directory.
* Ie, whether the contact is designed to only be visible outside search.
*/
public static final String IN_DEFAULT_DIRECTORY = "in_default_directory";
/**
* Flag that reflects the {@link Groups#GROUP_VISIBLE} state of any
* {@link CommonDataKinds.GroupMembership} for this contact.
*/
public static final String IN_VISIBLE_GROUP = "in_visible_group";
/**
* Flag that reflects whether this contact represents the user's
* personal profile entry.
*/
public static final String IS_USER_PROFILE = "is_user_profile";
/**
* An indicator of whether this contact has at least one phone number. "1" if there is
* at least one phone number, "0" otherwise.
* <P>Type: INTEGER</P>
*/
public static final String HAS_PHONE_NUMBER = "has_phone_number";
/**
* An opaque value that contains hints on how to find the contact if
* its row id changed as a result of a sync or aggregation.
*/
public static final String LOOKUP_KEY = "lookup";
/**
* Timestamp (milliseconds since epoch) of when this contact was last updated. This
* includes updates to all data associated with this contact including raw contacts. Any
* modification (including deletes and inserts) of underlying contact data are also
* reflected in this timestamp.
*/
public static final String CONTACT_LAST_UPDATED_TIMESTAMP =
"contact_last_updated_timestamp";
}
/**
* @see Contacts
*/
protected interface ContactStatusColumns {
/**
* Contact presence status. See {@link StatusUpdates} for individual status
* definitions.
* <p>Type: NUMBER</p>
*/
public static final String CONTACT_PRESENCE = "contact_presence";
/**
* Contact Chat Capabilities. See {@link StatusUpdates} for individual
* definitions.
* <p>Type: NUMBER</p>
*/
public static final String CONTACT_CHAT_CAPABILITY = "contact_chat_capability";
/**
* Contact's latest status update.
* <p>Type: TEXT</p>
*/
public static final String CONTACT_STATUS = "contact_status";
/**
* The absolute time in milliseconds when the latest status was
* inserted/updated.
* <p>Type: NUMBER</p>
*/
public static final String CONTACT_STATUS_TIMESTAMP = "contact_status_ts";
/**
* The package containing resources for this status: label and icon.
* <p>Type: TEXT</p>
*/
public static final String CONTACT_STATUS_RES_PACKAGE = "contact_status_res_package";
/**
* The resource ID of the label describing the source of contact
* status, e.g. "Google Talk". This resource is scoped by the
* {@link #CONTACT_STATUS_RES_PACKAGE}.
* <p>Type: NUMBER</p>
*/
public static final String CONTACT_STATUS_LABEL = "contact_status_label";
/**
* The resource ID of the icon for the source of contact status. This
* resource is scoped by the {@link #CONTACT_STATUS_RES_PACKAGE}.
* <p>Type: NUMBER</p>
*/
public static final String CONTACT_STATUS_ICON = "contact_status_icon";
}
/**
* Constants for various styles of combining given name, family name etc into
* a full name. For example, the western tradition follows the pattern
* 'given name' 'middle name' 'family name' with the alternative pattern being
* 'family name', 'given name' 'middle name'. The CJK tradition is
* 'family name' 'middle name' 'given name', with Japanese favoring a space between
* the names and Chinese omitting the space.
*/
public interface FullNameStyle {
public static final int UNDEFINED = 0;
public static final int WESTERN = 1;
/**
* Used if the name is written in Hanzi/Kanji/Hanja and we could not determine
* which specific language it belongs to: Chinese, Japanese or Korean.
*/
public static final int CJK = 2;
public static final int CHINESE = 3;
public static final int JAPANESE = 4;
public static final int KOREAN = 5;
}
/**
* Constants for various styles of capturing the pronunciation of a person's name.
*/
public interface PhoneticNameStyle {
public static final int UNDEFINED = 0;
/**
* Pinyin is a phonetic method of entering Chinese characters. Typically not explicitly
* shown in UIs, but used for searches and sorting.
*/
public static final int PINYIN = 3;
/**
* Hiragana and Katakana are two common styles of writing out the pronunciation
* of a Japanese names.
*/
public static final int JAPANESE = 4;
/**
* Hangul is the Korean phonetic alphabet.
*/
public static final int KOREAN = 5;
}
/**
* Types of data used to produce the display name for a contact. In the order
* of increasing priority: {@link #EMAIL}, {@link #PHONE},
* {@link #ORGANIZATION}, {@link #NICKNAME}, {@link #STRUCTURED_NAME}.
*/
public interface DisplayNameSources {
public static final int UNDEFINED = 0;
public static final int EMAIL = 10;
public static final int PHONE = 20;
public static final int ORGANIZATION = 30;
public static final int NICKNAME = 35;
public static final int STRUCTURED_NAME = 40;
}
/**
* Contact name and contact name metadata columns in the RawContacts table.
*
* @see Contacts
* @see RawContacts
*/
protected interface ContactNameColumns {
/**
* The kind of data that is used as the display name for the contact, such as
* structured name or email address. See {@link DisplayNameSources}.
*/
public static final String DISPLAY_NAME_SOURCE = "display_name_source";
/**
* <p>
* The standard text shown as the contact's display name, based on the best
* available information for the contact (for example, it might be the email address
* if the name is not available).
* The information actually used to compute the name is stored in
* {@link #DISPLAY_NAME_SOURCE}.
* </p>
* <p>
* A contacts provider is free to choose whatever representation makes most
* sense for its target market.
* For example in the default Android Open Source Project implementation,
* if the display name is
* based on the structured name and the structured name follows
* the Western full-name style, then this field contains the "given name first"
* version of the full name.
* <p>
*
* @see ContactsContract.ContactNameColumns#DISPLAY_NAME_ALTERNATIVE
*/
public static final String DISPLAY_NAME_PRIMARY = "display_name";
/**
* <p>
* An alternative representation of the display name, such as "family name first"
* instead of "given name first" for Western names. If an alternative is not
* available, the values should be the same as {@link #DISPLAY_NAME_PRIMARY}.
* </p>
* <p>
* A contacts provider is free to provide alternatives as necessary for
* its target market.
* For example the default Android Open Source Project contacts provider
* currently provides an
* alternative in a single case: if the display name is
* based on the structured name and the structured name follows
* the Western full name style, then the field contains the "family name first"
* version of the full name.
* Other cases may be added later.
* </p>
*/
public static final String DISPLAY_NAME_ALTERNATIVE = "display_name_alt";
/**
* The phonetic alphabet used to represent the {@link #PHONETIC_NAME}. See
* {@link PhoneticNameStyle}.
*/
public static final String PHONETIC_NAME_STYLE = "phonetic_name_style";
/**
* <p>
* Pronunciation of the full name in the phonetic alphabet specified by
* {@link #PHONETIC_NAME_STYLE}.
* </p>
* <p>
* The value may be set manually by the user. This capability is of
* interest only in countries with commonly used phonetic alphabets,
* such as Japan and Korea. See {@link PhoneticNameStyle}.
* </p>
*/
public static final String PHONETIC_NAME = "phonetic_name";
/**
* Sort key that takes into account locale-based traditions for sorting
* names in address books. The default
* sort key is {@link #DISPLAY_NAME_PRIMARY}. For Chinese names
* the sort key is the name's Pinyin spelling, and for Japanese names
* it is the Hiragana version of the phonetic name.
*/
public static final String SORT_KEY_PRIMARY = "sort_key";
/**
* Sort key based on the alternative representation of the full name,
* {@link #DISPLAY_NAME_ALTERNATIVE}. Thus for Western names,
* it is the one using the "family name first" format.
*/
public static final String SORT_KEY_ALTERNATIVE = "sort_key_alt";
}
interface ContactCounts {
/**
* Add this query parameter to a URI to get back row counts grouped by the address book
* index as cursor extras. For most languages it is the first letter of the sort key. This
* parameter does not affect the main content of the cursor.
*
* <p>
* <pre>
* Example:
*
* import android.provider.ContactsContract.Contacts;
*
* Uri uri = Contacts.CONTENT_URI.buildUpon()
* .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true")
* .build();
* Cursor cursor = getContentResolver().query(uri,
* new String[] {Contacts.DISPLAY_NAME},
* null, null, null);
* Bundle bundle = cursor.getExtras();
* if (bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) &&
* bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) {
* String sections[] =
* bundle.getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
* int counts[] = bundle.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
* }
* </pre>
* </p>
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX =
"android.provider.extra.ADDRESS_BOOK_INDEX";
/**
* The array of address book index titles, which are returned in the
* same order as the data in the cursor.
* <p>TYPE: String[]</p>
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES =
"android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
/**
* The array of group counts for the corresponding group. Contains the same number
* of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array.
* <p>TYPE: int[]</p>
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS =
"android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
}
/**
* Constants for the contacts table, which contains a record per aggregate
* of raw contacts representing the same person.
* <h3>Operations</h3>
* <dl>
* <dt><b>Insert</b></dt>
* <dd>A Contact cannot be created explicitly. When a raw contact is
* inserted, the provider will first try to find a Contact representing the
* same person. If one is found, the raw contact's
* {@link RawContacts#CONTACT_ID} column gets the _ID of the aggregate
* Contact. If no match is found, the provider automatically inserts a new
* Contact and puts its _ID into the {@link RawContacts#CONTACT_ID} column
* of the newly inserted raw contact.</dd>
* <dt><b>Update</b></dt>
* <dd>Only certain columns of Contact are modifiable:
* {@link #TIMES_CONTACTED}, {@link #LAST_TIME_CONTACTED}, {@link #STARRED},
* {@link #CUSTOM_RINGTONE}, {@link #SEND_TO_VOICEMAIL}. Changing any of
* these columns on the Contact also changes them on all constituent raw
* contacts.</dd>
* <dt><b>Delete</b></dt>
* <dd>Be careful with deleting Contacts! Deleting an aggregate contact
* deletes all constituent raw contacts. The corresponding sync adapters
* will notice the deletions of their respective raw contacts and remove
* them from their back end storage.</dd>
* <dt><b>Query</b></dt>
* <dd>
* <ul>
* <li>If you need to read an individual contact, consider using
* {@link #CONTENT_LOOKUP_URI} instead of {@link #CONTENT_URI}.</li>
* <li>If you need to look up a contact by the phone number, use
* {@link PhoneLookup#CONTENT_FILTER_URI PhoneLookup.CONTENT_FILTER_URI},
* which is optimized for this purpose.</li>
* <li>If you need to look up a contact by partial name, e.g. to produce
* filter-as-you-type suggestions, use the {@link #CONTENT_FILTER_URI} URI.
* <li>If you need to look up a contact by some data element like email
* address, nickname, etc, use a query against the {@link ContactsContract.Data} table.
* The result will contain contact ID, name etc.
* </ul>
* </dd>
* </dl>
* <h2>Columns</h2>
* <table class="jd-sumtable">
* <tr>
* <th colspan='4'>Contacts</th>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #_ID}</td>
* <td>read-only</td>
* <td>Row ID. Consider using {@link #LOOKUP_KEY} instead.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #LOOKUP_KEY}</td>
* <td>read-only</td>
* <td>An opaque value that contains hints on how to find the contact if its
* row id changed as a result of a sync or aggregation.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>NAME_RAW_CONTACT_ID</td>
* <td>read-only</td>
* <td>The ID of the raw contact that contributes the display name
* to the aggregate contact. During aggregation one of the constituent
* raw contacts is chosen using a heuristic: a longer name or a name
* with more diacritic marks or more upper case characters is chosen.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>DISPLAY_NAME_PRIMARY</td>
* <td>read-only</td>
* <td>The display name for the contact. It is the display name
* contributed by the raw contact referred to by the NAME_RAW_CONTACT_ID
* column.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #PHOTO_ID}</td>
* <td>read-only</td>
* <td>Reference to the row in the {@link ContactsContract.Data} table holding the photo.
* That row has the mime type
* {@link CommonDataKinds.Photo#CONTENT_ITEM_TYPE}. The value of this field
* is computed automatically based on the
* {@link CommonDataKinds.Photo#IS_SUPER_PRIMARY} field of the data rows of
* that mime type.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #PHOTO_URI}</td>
* <td>read-only</td>
* <td>A URI that can be used to retrieve the contact's full-size photo. This
* column is the preferred method of retrieving the contact photo.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #PHOTO_THUMBNAIL_URI}</td>
* <td>read-only</td>
* <td>A URI that can be used to retrieve the thumbnail of contact's photo. This
* column is the preferred method of retrieving the contact photo.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #IN_VISIBLE_GROUP}</td>
* <td>read-only</td>
* <td>An indicator of whether this contact is supposed to be visible in the
* UI. "1" if the contact has at least one raw contact that belongs to a
* visible group; "0" otherwise.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #HAS_PHONE_NUMBER}</td>
* <td>read-only</td>
* <td>An indicator of whether this contact has at least one phone number.
* "1" if there is at least one phone number, "0" otherwise.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #TIMES_CONTACTED}</td>
* <td>read/write</td>
* <td>The number of times the contact has been contacted. See
* {@link #markAsContacted}. When raw contacts are aggregated, this field is
* computed automatically as the maximum number of times contacted among all
* constituent raw contacts. Setting this field automatically changes the
* corresponding field on all constituent raw contacts.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #LAST_TIME_CONTACTED}</td>
* <td>read/write</td>
* <td>The timestamp of the last time the contact was contacted. See
* {@link #markAsContacted}. Setting this field also automatically
* increments {@link #TIMES_CONTACTED}. When raw contacts are aggregated,
* this field is computed automatically as the latest time contacted of all
* constituent raw contacts. Setting this field automatically changes the
* corresponding field on all constituent raw contacts.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #STARRED}</td>
* <td>read/write</td>
* <td>An indicator for favorite contacts: '1' if favorite, '0' otherwise.
* When raw contacts are aggregated, this field is automatically computed:
* if any constituent raw contacts are starred, then this field is set to
* '1'. Setting this field automatically changes the corresponding field on
* all constituent raw contacts.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #CUSTOM_RINGTONE}</td>
* <td>read/write</td>
* <td>A custom ringtone associated with a contact. Typically this is the
* URI returned by an activity launched with the
* {@link android.media.RingtoneManager#ACTION_RINGTONE_PICKER} intent.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #SEND_TO_VOICEMAIL}</td>
* <td>read/write</td>
* <td>An indicator of whether calls from this contact should be forwarded
* directly to voice mail ('1') or not ('0'). When raw contacts are
* aggregated, this field is automatically computed: if <i>all</i>
* constituent raw contacts have SEND_TO_VOICEMAIL=1, then this field is set
* to '1'. Setting this field automatically changes the corresponding field
* on all constituent raw contacts.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #CONTACT_PRESENCE}</td>
* <td>read-only</td>
* <td>Contact IM presence status. See {@link StatusUpdates} for individual
* status definitions. Automatically computed as the highest presence of all
* constituent raw contacts. The provider may choose not to store this value
* in persistent storage. The expectation is that presence status will be
* updated on a regular basis.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #CONTACT_STATUS}</td>
* <td>read-only</td>
* <td>Contact's latest status update. Automatically computed as the latest
* of all constituent raw contacts' status updates.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #CONTACT_STATUS_TIMESTAMP}</td>
* <td>read-only</td>
* <td>The absolute time in milliseconds when the latest status was
* inserted/updated.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #CONTACT_STATUS_RES_PACKAGE}</td>
* <td>read-only</td>
* <td> The package containing resources for this status: label and icon.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #CONTACT_STATUS_LABEL}</td>
* <td>read-only</td>
* <td>The resource ID of the label describing the source of contact status,
* e.g. "Google Talk". This resource is scoped by the
* {@link #CONTACT_STATUS_RES_PACKAGE}.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #CONTACT_STATUS_ICON}</td>
* <td>read-only</td>
* <td>The resource ID of the icon for the source of contact status. This
* resource is scoped by the {@link #CONTACT_STATUS_RES_PACKAGE}.</td>
* </tr>
* </table>
*/
public static class Contacts implements BaseColumns, ContactsColumns,
ContactOptionsColumns, ContactNameColumns, ContactStatusColumns, ContactCounts {
/**
* This utility class cannot be instantiated
*/
private Contacts() {}
/**
* The content:// style URI for this table
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "contacts");
/**
* Special contacts URI to refer to contacts on the corp profile from the personal
* profile.
*
* It's supported only by a few specific places for referring to contact pictures that
* are in the corp provider for enterprise caller-ID. Contact picture URIs returned from
* {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI} may contain this kind of URI.
*
* @hide
*/
public static final Uri CORP_CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,
"contacts_corp");
/**
* A content:// style URI for this table that should be used to create
* shortcuts or otherwise create long-term links to contacts. This URI
* should always be followed by a "/" and the contact's {@link #LOOKUP_KEY}.
* It can optionally also have a "/" and last known contact ID appended after
* that. This "complete" format is an important optimization and is highly recommended.
* <p>
* As long as the contact's row ID remains the same, this URI is
* equivalent to {@link #CONTENT_URI}. If the contact's row ID changes
* as a result of a sync or aggregation, this URI will look up the
* contact using indirect information (sync IDs or constituent raw
* contacts).
* <p>
* Lookup key should be appended unencoded - it is stored in the encoded
* form, ready for use in a URI.
*/
public static final Uri CONTENT_LOOKUP_URI = Uri.withAppendedPath(CONTENT_URI,
"lookup");
/**
* Base {@link Uri} for referencing a single {@link Contacts} entry,
* created by appending {@link #LOOKUP_KEY} using
* {@link Uri#withAppendedPath(Uri, String)}. Provides
* {@link OpenableColumns} columns when queried, or returns the
* referenced contact formatted as a vCard when opened through
* {@link ContentResolver#openAssetFileDescriptor(Uri, String)}.
*/
public static final Uri CONTENT_VCARD_URI = Uri.withAppendedPath(CONTENT_URI,
"as_vcard");
/**
* Boolean parameter that may be used with {@link #CONTENT_VCARD_URI}
* and {@link #CONTENT_MULTI_VCARD_URI} to indicate that the returned
* vcard should not contain a photo.
*
* @hide
*/
public static final String QUERY_PARAMETER_VCARD_NO_PHOTO = "nophoto";
/**
* Base {@link Uri} for referencing multiple {@link Contacts} entry,
* created by appending {@link #LOOKUP_KEY} using
* {@link Uri#withAppendedPath(Uri, String)}. The lookup keys have to be
* joined with the colon (":") separator, and the resulting string encoded.
*
* Provides {@link OpenableColumns} columns when queried, or returns the
* referenced contact formatted as a vCard when opened through
* {@link ContentResolver#openAssetFileDescriptor(Uri, String)}.
*
* <p>
* Usage example:
* <dl>
* <dt>The following code snippet creates a multi-vcard URI that references all the
* contacts in a user's database.</dt>
* <dd>
*
* <pre>
* public Uri getAllContactsVcardUri() {
* Cursor cursor = getActivity().getContentResolver().query(Contacts.CONTENT_URI,
* new String[] {Contacts.LOOKUP_KEY}, null, null, null);
* if (cursor == null) {
* return null;
* }
* try {
* StringBuilder uriListBuilder = new StringBuilder();
* int index = 0;
* while (cursor.moveToNext()) {
* if (index != 0) uriListBuilder.append(':');
* uriListBuilder.append(cursor.getString(0));
* index++;
* }
* return Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI,
* Uri.encode(uriListBuilder.toString()));
* } finally {
* cursor.close();
* }
* }
* </pre>
*
* </p>
*/
public static final Uri CONTENT_MULTI_VCARD_URI = Uri.withAppendedPath(CONTENT_URI,
"as_multi_vcard");
/**
* Builds a {@link #CONTENT_LOOKUP_URI} style {@link Uri} describing the
* requested {@link Contacts} entry.
*
* @param contactUri A {@link #CONTENT_URI} row, or an existing
* {@link #CONTENT_LOOKUP_URI} to attempt refreshing.
*/
public static Uri getLookupUri(ContentResolver resolver, Uri contactUri) {
final Cursor c = resolver.query(contactUri, new String[] {
Contacts.LOOKUP_KEY, Contacts._ID
}, null, null, null);
if (c == null) {
return null;
}
try {
if (c.moveToFirst()) {
final String lookupKey = c.getString(0);
final long contactId = c.getLong(1);
return getLookupUri(contactId, lookupKey);
}
} finally {
c.close();
}
return null;
}
/**
* Build a {@link #CONTENT_LOOKUP_URI} lookup {@link Uri} using the
* given {@link ContactsContract.Contacts#_ID} and {@link #LOOKUP_KEY}.
*/
public static Uri getLookupUri(long contactId, String lookupKey) {
return ContentUris.withAppendedId(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
lookupKey), contactId);
}
/**
* Computes a content URI (see {@link #CONTENT_URI}) given a lookup URI.
* <p>
* Returns null if the contact cannot be found.
*/
public static Uri lookupContact(ContentResolver resolver, Uri lookupUri) {
if (lookupUri == null) {
return null;
}
Cursor c = resolver.query(lookupUri, new String[]{Contacts._ID}, null, null, null);
if (c == null) {
return null;
}
try {
if (c.moveToFirst()) {
long contactId = c.getLong(0);
return ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
}
} finally {
c.close();
}
return null;
}
/**
* Mark a contact as having been contacted. Updates two fields:
* {@link #TIMES_CONTACTED} and {@link #LAST_TIME_CONTACTED}. The
* TIMES_CONTACTED field is incremented by 1 and the LAST_TIME_CONTACTED
* field is populated with the current system time.
*
* @param resolver the ContentResolver to use
* @param contactId the person who was contacted
*
* @deprecated The class DataUsageStatUpdater of the Android support library should
* be used instead.
*/
@Deprecated
public static void markAsContacted(ContentResolver resolver, long contactId) {
Uri uri = ContentUris.withAppendedId(CONTENT_URI, contactId);
ContentValues values = new ContentValues();
// TIMES_CONTACTED will be incremented when LAST_TIME_CONTACTED is modified.
values.put(LAST_TIME_CONTACTED, System.currentTimeMillis());
resolver.update(uri, values, null, null);
}
/**
* The content:// style URI used for "type-to-filter" functionality on the
* {@link #CONTENT_URI} URI. The filter string will be used to match
* various parts of the contact name. The filter argument should be passed
* as an additional path segment after this URI.
*/
public static final Uri CONTENT_FILTER_URI = Uri.withAppendedPath(
CONTENT_URI, "filter");
/**
* The content:// style URI for this table joined with useful data from
* {@link ContactsContract.Data}, filtered to include only starred contacts
* and the most frequently contacted contacts.
*/
public static final Uri CONTENT_STREQUENT_URI = Uri.withAppendedPath(
CONTENT_URI, "strequent");
/**
* The content:// style URI for showing a list of frequently contacted people.
*/
public static final Uri CONTENT_FREQUENT_URI = Uri.withAppendedPath(
CONTENT_URI, "frequent");
/**
* The content:// style URI used for "type-to-filter" functionality on the
* {@link #CONTENT_STREQUENT_URI} URI. The filter string will be used to match
* various parts of the contact name. The filter argument should be passed
* as an additional path segment after this URI.
*/
public static final Uri CONTENT_STREQUENT_FILTER_URI = Uri.withAppendedPath(
CONTENT_STREQUENT_URI, "filter");
public static final Uri CONTENT_GROUP_URI = Uri.withAppendedPath(
CONTENT_URI, "group");
/**
* The MIME type of {@link #CONTENT_URI} providing a directory of
* people.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact";
/**
* The MIME type of a {@link #CONTENT_URI} subdirectory of a single
* person.
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact";
/**
* The MIME type of a {@link #CONTENT_URI} subdirectory of a single
* person.
*/
public static final String CONTENT_VCARD_TYPE = "text/x-vcard";
/**
* Mimimal ID for corp contacts returned from
* {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
*
* @hide
*/
public static long ENTERPRISE_CONTACT_ID_BASE = 1000000000; // slightly smaller than 2 ** 30
/**
* Return TRUE if a contact ID is from the contacts provider on the enterprise profile.
*
* {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI} may return such a contact.
*/
public static boolean isEnterpriseContactId(long contactId) {
return (contactId >= ENTERPRISE_CONTACT_ID_BASE) && (contactId < Profile.MIN_ID);
}
/**
* A sub-directory of a single contact that contains all of the constituent raw contact
* {@link ContactsContract.Data} rows. This directory can be used either
* with a {@link #CONTENT_URI} or {@link #CONTENT_LOOKUP_URI}.
*/
public static final class Data implements BaseColumns, DataColumns {
/**
* no public constructor since this is a utility class
*/
private Data() {}
/**
* The directory twig for this sub-table
*/
public static final String CONTENT_DIRECTORY = "data";
}
/**
* <p>
* A sub-directory of a contact that contains all of its
* {@link ContactsContract.RawContacts} as well as
* {@link ContactsContract.Data} rows. To access this directory append
* {@link #CONTENT_DIRECTORY} to the contact URI.
* </p>
* <p>
* Entity has three ID fields: {@link #CONTACT_ID} for the contact,
* {@link #RAW_CONTACT_ID} for the raw contact and {@link #DATA_ID} for
* the data rows. Entity always contains at least one row per
* constituent raw contact, even if there are no actual data rows. In
* this case the {@link #DATA_ID} field will be null.
* </p>
* <p>
* Entity reads all data for the entire contact in one transaction, to
* guarantee consistency. There is significant data duplication
* in the Entity (each row repeats all Contact columns and all RawContact
* columns), so the benefits of transactional consistency should be weighed
* against the cost of transferring large amounts of denormalized data
* from the Provider.
* </p>
* <p>
* To reduce the amount of data duplication the contacts provider and directory
* providers implementing this protocol are allowed to provide common Contacts
* and RawContacts fields in the first row returned for each raw contact only and
* leave them as null in subsequent rows.
* </p>
*/
public static final class Entity implements BaseColumns, ContactsColumns,
ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns,
StatusColumns, ContactOptionsColumns, ContactStatusColumns, DataUsageStatColumns {
/**
* no public constructor since this is a utility class
*/
private Entity() {
}
/**
* The directory twig for this sub-table
*/
public static final String CONTENT_DIRECTORY = "entities";
/**
* The ID of the raw contact row.
* <P>Type: INTEGER</P>
*/
public static final String RAW_CONTACT_ID = "raw_contact_id";
/**
* The ID of the data row. The value will be null if this raw contact has no
* data rows.
* <P>Type: INTEGER</P>
*/
public static final String DATA_ID = "data_id";
}
/**
* <p>
* A sub-directory of a single contact that contains all of the constituent raw contact
* {@link ContactsContract.StreamItems} rows. This directory can be used either
* with a {@link #CONTENT_URI} or {@link #CONTENT_LOOKUP_URI}.
* </p>
* <p>
* Querying for social stream data requires android.permission.READ_SOCIAL_STREAM
* permission.
* </p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final class StreamItems implements StreamItemsColumns {
/**
* no public constructor since this is a utility class
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
private StreamItems() {}
/**
* The directory twig for this sub-table
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTENT_DIRECTORY = "stream_items";
}
/**
* <p>
* A <i>read-only</i> sub-directory of a single contact aggregate that
* contains all aggregation suggestions (other contacts). The
* aggregation suggestions are computed based on approximate data
* matches with this contact.
* </p>
* <p>
* <i>Note: this query may be expensive! If you need to use it in bulk,
* make sure the user experience is acceptable when the query runs for a
* long time.</i>
* <p>
* Usage example:
*
* <pre>
* Uri uri = Contacts.CONTENT_URI.buildUpon()
* .appendEncodedPath(String.valueOf(contactId))
* .appendPath(Contacts.AggregationSuggestions.CONTENT_DIRECTORY)
* .appendQueryParameter("limit", "3")
* .build()
* Cursor cursor = getContentResolver().query(suggestionsUri,
* new String[] {Contacts.DISPLAY_NAME, Contacts._ID, Contacts.LOOKUP_KEY},
* null, null, null);
* </pre>
*
* </p>
* <p>
* This directory can be used either with a {@link #CONTENT_URI} or
* {@link #CONTENT_LOOKUP_URI}.
* </p>
*/
public static final class AggregationSuggestions implements BaseColumns, ContactsColumns,
ContactOptionsColumns, ContactStatusColumns {
/**
* No public constructor since this is a utility class
*/
private AggregationSuggestions() {}
/**
* The directory twig for this sub-table. The URI can be followed by an optional
* type-to-filter, similar to
* {@link android.provider.ContactsContract.Contacts#CONTENT_FILTER_URI}.
*/
public static final String CONTENT_DIRECTORY = "suggestions";
/**
* Used with {@link Builder#addParameter} to specify what kind of data is
* supplied for the suggestion query.
*
* @hide
*/
public static final String PARAMETER_MATCH_NAME = "name";
/**
* Used with {@link Builder#addParameter} to specify what kind of data is
* supplied for the suggestion query.
*
* @hide
*/
public static final String PARAMETER_MATCH_EMAIL = "email";
/**
* Used with {@link Builder#addParameter} to specify what kind of data is
* supplied for the suggestion query.
*
* @hide
*/
public static final String PARAMETER_MATCH_PHONE = "phone";
/**
* Used with {@link Builder#addParameter} to specify what kind of data is
* supplied for the suggestion query.
*
* @hide
*/
public static final String PARAMETER_MATCH_NICKNAME = "nickname";
/**
* A convenience builder for aggregation suggestion content URIs.
*
* TODO: change documentation for this class to use the builder.
* @hide
*/
public static final class Builder {
private long mContactId;
private ArrayList<String> mKinds = new ArrayList<String>();
private ArrayList<String> mValues = new ArrayList<String>();
private int mLimit;
/**
* Optional existing contact ID. If it is not provided, the search
* will be based exclusively on the values supplied with {@link #addParameter}.
*/
public Builder setContactId(long contactId) {
this.mContactId = contactId;
return this;
}
/**
* A value that can be used when searching for an aggregation
* suggestion.
*
* @param kind can be one of
* {@link AggregationSuggestions#PARAMETER_MATCH_NAME},
* {@link AggregationSuggestions#PARAMETER_MATCH_EMAIL},
* {@link AggregationSuggestions#PARAMETER_MATCH_NICKNAME},
* {@link AggregationSuggestions#PARAMETER_MATCH_PHONE}
*/
public Builder addParameter(String kind, String value) {
if (!TextUtils.isEmpty(value)) {
mKinds.add(kind);
mValues.add(value);
}
return this;
}
public Builder setLimit(int limit) {
mLimit = limit;
return this;
}
public Uri build() {
android.net.Uri.Builder builder = Contacts.CONTENT_URI.buildUpon();
builder.appendEncodedPath(String.valueOf(mContactId));
builder.appendPath(Contacts.AggregationSuggestions.CONTENT_DIRECTORY);
if (mLimit != 0) {
builder.appendQueryParameter("limit", String.valueOf(mLimit));
}
int count = mKinds.size();
for (int i = 0; i < count; i++) {
builder.appendQueryParameter("query", mKinds.get(i) + ":" + mValues.get(i));
}
return builder.build();
}
}
/**
* @hide
*/
public static final Builder builder() {
return new Builder();
}
}
/**
* A <i>read-only</i> sub-directory of a single contact that contains
* the contact's primary photo. The photo may be stored in up to two ways -
* the default "photo" is a thumbnail-sized image stored directly in the data
* row, while the "display photo", if present, is a larger version stored as
* a file.
* <p>
* Usage example:
* <dl>
* <dt>Retrieving the thumbnail-sized photo</dt>
* <dd>
* <pre>
* public InputStream openPhoto(long contactId) {
* Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
* Uri photoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);
* Cursor cursor = getContentResolver().query(photoUri,
* new String[] {Contacts.Photo.PHOTO}, null, null, null);
* if (cursor == null) {
* return null;
* }
* try {
* if (cursor.moveToFirst()) {
* byte[] data = cursor.getBlob(0);
* if (data != null) {
* return new ByteArrayInputStream(data);
* }
* }
* } finally {
* cursor.close();
* }
* return null;
* }
* </pre>
* </dd>
* <dt>Retrieving the larger photo version</dt>
* <dd>
* <pre>
* public InputStream openDisplayPhoto(long contactId) {
* Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
* Uri displayPhotoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.DISPLAY_PHOTO);
* try {
* AssetFileDescriptor fd =
* getContentResolver().openAssetFileDescriptor(displayPhotoUri, "r");
* return fd.createInputStream();
* } catch (IOException e) {
* return null;
* }
* }
* </pre>
* </dd>
* </dl>
*
* </p>
* <p>You may also consider using the convenience method
* {@link ContactsContract.Contacts#openContactPhotoInputStream(ContentResolver, Uri, boolean)}
* to retrieve the raw photo contents of either the thumbnail-sized or the full-sized photo.
* </p>
* <p>
* This directory can be used either with a {@link #CONTENT_URI} or
* {@link #CONTENT_LOOKUP_URI}.
* </p>
*/
public static final class Photo implements BaseColumns, DataColumnsWithJoins {
/**
* no public constructor since this is a utility class
*/
private Photo() {}
/**
* The directory twig for this sub-table
*/
public static final String CONTENT_DIRECTORY = "photo";
/**
* The directory twig for retrieving the full-size display photo.
*/
public static final String DISPLAY_PHOTO = "display_photo";
/**
* Full-size photo file ID of the raw contact.
* See {@link ContactsContract.DisplayPhoto}.
* <p>
* Type: NUMBER
*/
public static final String PHOTO_FILE_ID = DATA14;
/**
* Thumbnail photo of the raw contact. This is the raw bytes of an image
* that could be inflated using {@link android.graphics.BitmapFactory}.
* <p>
* Type: BLOB
*/
public static final String PHOTO = DATA15;
}
/**
* Opens an InputStream for the contacts's photo and returns the
* photo as a byte stream.
* @param cr The content resolver to use for querying
* @param contactUri the contact whose photo should be used. This can be used with
* either a {@link #CONTENT_URI} or a {@link #CONTENT_LOOKUP_URI} URI.
* @param preferHighres If this is true and the contact has a higher resolution photo
* available, it is returned. If false, this function always tries to get the thumbnail
* @return an InputStream of the photo, or null if no photo is present
*/
public static InputStream openContactPhotoInputStream(ContentResolver cr, Uri contactUri,
boolean preferHighres) {
if (preferHighres) {
final Uri displayPhotoUri = Uri.withAppendedPath(contactUri,
Contacts.Photo.DISPLAY_PHOTO);
InputStream inputStream;
try {
AssetFileDescriptor fd = cr.openAssetFileDescriptor(displayPhotoUri, "r");
return fd.createInputStream();
} catch (IOException e) {
// fallback to the thumbnail code
}
}
Uri photoUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY);
if (photoUri == null) {
return null;
}
Cursor cursor = cr.query(photoUri,
new String[] {
ContactsContract.CommonDataKinds.Photo.PHOTO
}, null, null, null);
try {
if (cursor == null || !cursor.moveToNext()) {
return null;
}
byte[] data = cursor.getBlob(0);
if (data == null) {
return null;
}
return new ByteArrayInputStream(data);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/**
* Opens an InputStream for the contacts's thumbnail photo and returns the
* photo as a byte stream.
* @param cr The content resolver to use for querying
* @param contactUri the contact whose photo should be used. This can be used with
* either a {@link #CONTENT_URI} or a {@link #CONTENT_LOOKUP_URI} URI.
* @return an InputStream of the photo, or null if no photo is present
* @see #openContactPhotoInputStream(ContentResolver, Uri, boolean), if instead
* of the thumbnail the high-res picture is preferred
*/
public static InputStream openContactPhotoInputStream(ContentResolver cr, Uri contactUri) {
return openContactPhotoInputStream(cr, contactUri, false);
}
}
/**
* <p>
* Constants for the user's profile data, which is represented as a single contact on
* the device that represents the user. The profile contact is not aggregated
* together automatically in the same way that normal contacts are; instead, each
* account (including data set, if applicable) on the device may contribute a single
* raw contact representing the user's personal profile data from that source.
* </p>
* <p>
* Access to the profile entry through these URIs (or incidental access to parts of
* the profile if retrieved directly via ID) requires additional permissions beyond
* the read/write contact permissions required by the provider. Querying for profile
* data requires android.permission.READ_PROFILE permission, and inserting or
* updating profile data requires android.permission.WRITE_PROFILE permission.
* </p>
* <h3>Operations</h3>
* <dl>
* <dt><b>Insert</b></dt>
* <dd>The user's profile entry cannot be created explicitly (attempting to do so
* will throw an exception). When a raw contact is inserted into the profile, the
* provider will check for the existence of a profile on the device. If one is
* found, the raw contact's {@link RawContacts#CONTACT_ID} column gets the _ID of
* the profile Contact. If no match is found, the profile Contact is created and
* its _ID is put into the {@link RawContacts#CONTACT_ID} column of the newly
* inserted raw contact.</dd>
* <dt><b>Update</b></dt>
* <dd>The profile Contact has the same update restrictions as Contacts in general,
* but requires the android.permission.WRITE_PROFILE permission.</dd>
* <dt><b>Delete</b></dt>
* <dd>The profile Contact cannot be explicitly deleted. It will be removed
* automatically if all of its constituent raw contact entries are deleted.</dd>
* <dt><b>Query</b></dt>
* <dd>
* <ul>
* <li>The {@link #CONTENT_URI} for profiles behaves in much the same way as
* retrieving a contact by ID, except that it will only ever return the user's
* profile contact.
* </li>
* <li>
* The profile contact supports all of the same sub-paths as an individual contact
* does - the content of the profile contact can be retrieved as entities or
* data rows. Similarly, specific raw contact entries can be retrieved by appending
* the desired raw contact ID within the profile.
* </li>
* </ul>
* </dd>
* </dl>
*/
public static final class Profile implements BaseColumns, ContactsColumns,
ContactOptionsColumns, ContactNameColumns, ContactStatusColumns {
/**
* This utility class cannot be instantiated
*/
private Profile() {
}
/**
* The content:// style URI for this table, which requests the contact entry
* representing the user's personal profile data.
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "profile");
/**
* {@link Uri} for referencing the user's profile {@link Contacts} entry,
* Provides {@link OpenableColumns} columns when queried, or returns the
* user's profile contact formatted as a vCard when opened through
* {@link ContentResolver#openAssetFileDescriptor(Uri, String)}.
*/
public static final Uri CONTENT_VCARD_URI = Uri.withAppendedPath(CONTENT_URI,
"as_vcard");
/**
* {@link Uri} for referencing the raw contacts that make up the user's profile
* {@link Contacts} entry. An individual raw contact entry within the profile
* can be addressed by appending the raw contact ID. The entities or data within
* that specific raw contact can be requested by appending the entity or data
* path as well.
*/
public static final Uri CONTENT_RAW_CONTACTS_URI = Uri.withAppendedPath(CONTENT_URI,
"raw_contacts");
/**
* The minimum ID for any entity that belongs to the profile. This essentially
* defines an ID-space in which profile data is stored, and is used by the provider
* to determine whether a request via a non-profile-specific URI should be directed
* to the profile data rather than general contacts data, along with all the special
* permission checks that entails.
*
* Callers may use {@link #isProfileId} to check whether a specific ID falls into
* the set of data intended for the profile.
*/
public static final long MIN_ID = Long.MAX_VALUE - (long) Integer.MAX_VALUE;
}
/**
* This method can be used to identify whether the given ID is associated with profile
* data. It does not necessarily indicate that the ID is tied to valid data, merely
* that accessing data using this ID will result in profile access checks and will only
* return data from the profile.
*
* @param id The ID to check.
* @return Whether the ID is associated with profile data.
*/
public static boolean isProfileId(long id) {
return id >= Profile.MIN_ID;
}
protected interface DeletedContactsColumns {
/**
* A reference to the {@link ContactsContract.Contacts#_ID} that was deleted.
* <P>Type: INTEGER</P>
*/
public static final String CONTACT_ID = "contact_id";
/**
* Time (milliseconds since epoch) that the contact was deleted.
*/
public static final String CONTACT_DELETED_TIMESTAMP = "contact_deleted_timestamp";
}
/**
* Constants for the deleted contact table. This table holds a log of deleted contacts.
* <p>
* Log older than {@link #DAYS_KEPT_MILLISECONDS} may be deleted.
*/
public static final class DeletedContacts implements DeletedContactsColumns {
/**
* This utility class cannot be instantiated
*/
private DeletedContacts() {
}
/**
* The content:// style URI for this table, which requests a directory of raw contact rows
* matching the selection criteria.
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,
"deleted_contacts");
/**
* Number of days that the delete log will be kept. After this time, delete records may be
* deleted.
*
* @hide
*/
private static final int DAYS_KEPT = 30;
/**
* Milliseconds that the delete log will be kept. After this time, delete records may be
* deleted.
*/
public static final long DAYS_KEPT_MILLISECONDS = 1000L * 60L * 60L * 24L * (long)DAYS_KEPT;
}
protected interface RawContactsColumns {
/**
* A reference to the {@link ContactsContract.Contacts#_ID} that this
* data belongs to.
* <P>Type: INTEGER</P>
*/
public static final String CONTACT_ID = "contact_id";
/**
* The data set within the account that this row belongs to. This allows
* multiple sync adapters for the same account type to distinguish between
* each others' data.
*
* This is empty by default, and is completely optional. It only needs to
* be populated if multiple sync adapters are entering distinct data for
* the same account type and account name.
* <P>Type: TEXT</P>
*/
public static final String DATA_SET = "data_set";
/**
* A concatenation of the account type and data set (delimited by a forward
* slash) - if the data set is empty, this will be the same as the account
* type. For applications that need to be aware of the data set, this can
* be used instead of account type to distinguish sets of data. This is
* never intended to be used for specifying accounts.
* <p>
* This column does *not* escape forward slashes in the account type or the data set.
* If this is an issue, consider using
* {@link ContactsContract.RawContacts#ACCOUNT_TYPE} and
* {@link ContactsContract.RawContacts#DATA_SET} directly.
*/
public static final String ACCOUNT_TYPE_AND_DATA_SET = "account_type_and_data_set";
/**
* The aggregation mode for this contact.
* <P>Type: INTEGER</P>
*/
public static final String AGGREGATION_MODE = "aggregation_mode";
/**
* The "deleted" flag: "0" by default, "1" if the row has been marked
* for deletion. When {@link android.content.ContentResolver#delete} is
* called on a raw contact, it is marked for deletion and removed from its
* aggregate contact. The sync adaptor deletes the raw contact on the server and
* then calls ContactResolver.delete once more, this time passing the
* {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter to finalize
* the data removal.
* <P>Type: INTEGER</P>
*/
public static final String DELETED = "deleted";
/**
* The "name_verified" flag: "1" means that the name fields on this raw
* contact can be trusted and therefore should be used for the entire
* aggregated contact.
* <p>
* If an aggregated contact contains more than one raw contact with a
* verified name, one of those verified names is chosen at random.
* If an aggregated contact contains no verified names, the
* name is chosen randomly from the constituent raw contacts.
* </p>
* <p>
* Updating this flag from "0" to "1" automatically resets it to "0" on
* all other raw contacts in the same aggregated contact.
* </p>
* <p>
* Sync adapters should only specify a value for this column when
* inserting a raw contact and leave it out when doing an update.
* </p>
* <p>
* The default value is "0"
* </p>
* <p>Type: INTEGER</p>
*
* @hide
*/
public static final String NAME_VERIFIED = "name_verified";
/**
* The "read-only" flag: "0" by default, "1" if the row cannot be modified or
* deleted except by a sync adapter. See {@link ContactsContract#CALLER_IS_SYNCADAPTER}.
* <P>Type: INTEGER</P>
*/
public static final String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
/**
* Flag that reflects whether this raw contact belongs to the user's
* personal profile entry.
*/
public static final String RAW_CONTACT_IS_USER_PROFILE = "raw_contact_is_user_profile";
}
/**
* Constants for the raw contacts table, which contains one row of contact
* information for each person in each synced account. Sync adapters and
* contact management apps
* are the primary consumers of this API.
*
* <h3>Aggregation</h3>
* <p>
* As soon as a raw contact is inserted or whenever its constituent data
* changes, the provider will check if the raw contact matches other
* existing raw contacts and if so will aggregate it with those. The
* aggregation is reflected in the {@link RawContacts} table by the change of the
* {@link #CONTACT_ID} field, which is the reference to the aggregate contact.
* </p>
* <p>
* Changes to the structured name, organization, phone number, email address,
* or nickname trigger a re-aggregation.
* </p>
* <p>
* See also {@link AggregationExceptions} for a mechanism to control
* aggregation programmatically.
* </p>
*
* <h3>Operations</h3>
* <dl>
* <dt><b>Insert</b></dt>
* <dd>
* <p>
* Raw contacts can be inserted incrementally or in a batch.
* The incremental method is more traditional but less efficient.
* It should be used
* only if no {@link Data} values are available at the time the raw contact is created:
* <pre>
* ContentValues values = new ContentValues();
* values.put(RawContacts.ACCOUNT_TYPE, accountType);
* values.put(RawContacts.ACCOUNT_NAME, accountName);
* Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values);
* long rawContactId = ContentUris.parseId(rawContactUri);
* </pre>
* </p>
* <p>
* Once {@link Data} values become available, insert those.
* For example, here's how you would insert a name:
*
* <pre>
* values.clear();
* values.put(Data.RAW_CONTACT_ID, rawContactId);
* values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
* values.put(StructuredName.DISPLAY_NAME, "Mike Sullivan");
* getContentResolver().insert(Data.CONTENT_URI, values);
* </pre>
* </p>
* <p>
* The batch method is by far preferred. It inserts the raw contact and its
* constituent data rows in a single database transaction
* and causes at most one aggregation pass.
* <pre>
* ArrayList<ContentProviderOperation> ops =
* new ArrayList<ContentProviderOperation>();
* ...
* int rawContactInsertIndex = ops.size();
* ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
* .withValue(RawContacts.ACCOUNT_TYPE, accountType)
* .withValue(RawContacts.ACCOUNT_NAME, accountName)
* .build());
*
* ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
* .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex)
* .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
* .withValue(StructuredName.DISPLAY_NAME, "Mike Sullivan")
* .build());
*
* getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
* </pre>
* </p>
* <p>
* Note the use of {@link ContentProviderOperation.Builder#withValueBackReference(String, int)}
* to refer to the as-yet-unknown index value of the raw contact inserted in the
* first operation.
* </p>
*
* <dt><b>Update</b></dt>
* <dd><p>
* Raw contacts can be updated incrementally or in a batch.
* Batch mode should be used whenever possible.
* The procedures and considerations are analogous to those documented above for inserts.
* </p></dd>
* <dt><b>Delete</b></dt>
* <dd><p>When a raw contact is deleted, all of its Data rows as well as StatusUpdates,
* AggregationExceptions, PhoneLookup rows are deleted automatically. When all raw
* contacts associated with a {@link Contacts} row are deleted, the {@link Contacts} row
* itself is also deleted automatically.
* </p>
* <p>
* The invocation of {@code resolver.delete(...)}, does not immediately delete
* a raw contacts row.
* Instead, it sets the {@link #DELETED} flag on the raw contact and
* removes the raw contact from its aggregate contact.
* The sync adapter then deletes the raw contact from the server and
* finalizes phone-side deletion by calling {@code resolver.delete(...)}
* again and passing the {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter.<p>
* <p>Some sync adapters are read-only, meaning that they only sync server-side
* changes to the phone, but not the reverse. If one of those raw contacts
* is marked for deletion, it will remain on the phone. However it will be
* effectively invisible, because it will not be part of any aggregate contact.
* </dd>
*
* <dt><b>Query</b></dt>
* <dd>
* <p>
* It is easy to find all raw contacts in a Contact:
* <pre>
* Cursor c = getContentResolver().query(RawContacts.CONTENT_URI,
* new String[]{RawContacts._ID},
* RawContacts.CONTACT_ID + "=?",
* new String[]{String.valueOf(contactId)}, null);
* </pre>
* </p>
* <p>
* To find raw contacts within a specific account,
* you can either put the account name and type in the selection or pass them as query
* parameters. The latter approach is preferable, especially when you can reuse the
* URI:
* <pre>
* Uri rawContactUri = RawContacts.CONTENT_URI.buildUpon()
* .appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName)
* .appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType)
* .build();
* Cursor c1 = getContentResolver().query(rawContactUri,
* RawContacts.STARRED + "<>0", null, null, null);
* ...
* Cursor c2 = getContentResolver().query(rawContactUri,
* RawContacts.DELETED + "<>0", null, null, null);
* </pre>
* </p>
* <p>The best way to read a raw contact along with all the data associated with it is
* by using the {@link Entity} directory. If the raw contact has data rows,
* the Entity cursor will contain a row for each data row. If the raw contact has no
* data rows, the cursor will still contain one row with the raw contact-level information.
* <pre>
* Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
* Uri entityUri = Uri.withAppendedPath(rawContactUri, Entity.CONTENT_DIRECTORY);
* Cursor c = getContentResolver().query(entityUri,
* new String[]{RawContacts.SOURCE_ID, Entity.DATA_ID, Entity.MIMETYPE, Entity.DATA1},
* null, null, null);
* try {
* while (c.moveToNext()) {
* String sourceId = c.getString(0);
* if (!c.isNull(1)) {
* String mimeType = c.getString(2);
* String data = c.getString(3);
* ...
* }
* }
* } finally {
* c.close();
* }
* </pre>
* </p>
* </dd>
* </dl>
* <h2>Columns</h2>
*
* <table class="jd-sumtable">
* <tr>
* <th colspan='4'>RawContacts</th>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #_ID}</td>
* <td>read-only</td>
* <td>Row ID. Sync adapters should try to preserve row IDs during updates. In other words,
* it is much better for a sync adapter to update a raw contact rather than to delete and
* re-insert it.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #CONTACT_ID}</td>
* <td>read-only</td>
* <td>The ID of the row in the {@link ContactsContract.Contacts} table
* that this raw contact belongs
* to. Raw contacts are linked to contacts by the aggregation process, which can be controlled
* by the {@link #AGGREGATION_MODE} field and {@link AggregationExceptions}.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #AGGREGATION_MODE}</td>
* <td>read/write</td>
* <td>A mechanism that allows programmatic control of the aggregation process. The allowed
* values are {@link #AGGREGATION_MODE_DEFAULT}, {@link #AGGREGATION_MODE_DISABLED}
* and {@link #AGGREGATION_MODE_SUSPENDED}. See also {@link AggregationExceptions}.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #DELETED}</td>
* <td>read/write</td>
* <td>The "deleted" flag: "0" by default, "1" if the row has been marked
* for deletion. When {@link android.content.ContentResolver#delete} is
* called on a raw contact, it is marked for deletion and removed from its
* aggregate contact. The sync adaptor deletes the raw contact on the server and
* then calls ContactResolver.delete once more, this time passing the
* {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter to finalize
* the data removal.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #TIMES_CONTACTED}</td>
* <td>read/write</td>
* <td>The number of times the contact has been contacted. To have an effect
* on the corresponding value of the aggregate contact, this field
* should be set at the time the raw contact is inserted.
* After that, this value is typically updated via
* {@link ContactsContract.Contacts#markAsContacted}.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #LAST_TIME_CONTACTED}</td>
* <td>read/write</td>
* <td>The timestamp of the last time the contact was contacted. To have an effect
* on the corresponding value of the aggregate contact, this field
* should be set at the time the raw contact is inserted.
* After that, this value is typically updated via
* {@link ContactsContract.Contacts#markAsContacted}.
* </td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #STARRED}</td>
* <td>read/write</td>
* <td>An indicator for favorite contacts: '1' if favorite, '0' otherwise.
* Changing this field immediately affects the corresponding aggregate contact:
* if any raw contacts in that aggregate contact are starred, then the contact
* itself is marked as starred.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #CUSTOM_RINGTONE}</td>
* <td>read/write</td>
* <td>A custom ringtone associated with a raw contact. Typically this is the
* URI returned by an activity launched with the
* {@link android.media.RingtoneManager#ACTION_RINGTONE_PICKER} intent.
* To have an effect on the corresponding value of the aggregate contact, this field
* should be set at the time the raw contact is inserted. To set a custom
* ringtone on a contact, use the field {@link ContactsContract.Contacts#CUSTOM_RINGTONE
* Contacts.CUSTOM_RINGTONE}
* instead.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #SEND_TO_VOICEMAIL}</td>
* <td>read/write</td>
* <td>An indicator of whether calls from this raw contact should be forwarded
* directly to voice mail ('1') or not ('0'). To have an effect
* on the corresponding value of the aggregate contact, this field
* should be set at the time the raw contact is inserted.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #ACCOUNT_NAME}</td>
* <td>read/write-once</td>
* <td>The name of the account instance to which this row belongs, which when paired with
* {@link #ACCOUNT_TYPE} identifies a specific account.
* For example, this will be the Gmail address if it is a Google account.
* It should be set at the time the raw contact is inserted and never
* changed afterwards.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #ACCOUNT_TYPE}</td>
* <td>read/write-once</td>
* <td>
* <p>
* The type of account to which this row belongs, which when paired with
* {@link #ACCOUNT_NAME} identifies a specific account.
* It should be set at the time the raw contact is inserted and never
* changed afterwards.
* </p>
* <p>
* To ensure uniqueness, new account types should be chosen according to the
* Java package naming convention. Thus a Google account is of type "com.google".
* </p>
* </td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #DATA_SET}</td>
* <td>read/write-once</td>
* <td>
* <p>
* The data set within the account that this row belongs to. This allows
* multiple sync adapters for the same account type to distinguish between
* each others' data. The combination of {@link #ACCOUNT_TYPE},
* {@link #ACCOUNT_NAME}, and {@link #DATA_SET} identifies a set of data
* that is associated with a single sync adapter.
* </p>
* <p>
* This is empty by default, and is completely optional. It only needs to
* be populated if multiple sync adapters are entering distinct data for
* the same account type and account name.
* </p>
* <p>
* It should be set at the time the raw contact is inserted and never
* changed afterwards.
* </p>
* </td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #SOURCE_ID}</td>
* <td>read/write</td>
* <td>String that uniquely identifies this row to its source account.
* Typically it is set at the time the raw contact is inserted and never
* changed afterwards. The one notable exception is a new raw contact: it
* will have an account name and type (and possibly a data set), but no
* source id. This indicates to the sync adapter that a new contact needs
* to be created server-side and its ID stored in the corresponding
* SOURCE_ID field on the phone.
* </td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #VERSION}</td>
* <td>read-only</td>
* <td>Version number that is updated whenever this row or its related data
* changes. This field can be used for optimistic locking of a raw contact.
* </td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #DIRTY}</td>
* <td>read/write</td>
* <td>Flag indicating that {@link #VERSION} has changed, and this row needs
* to be synchronized by its owning account. The value is set to "1" automatically
* whenever the raw contact changes, unless the URI has the
* {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter specified.
* The sync adapter should always supply this query parameter to prevent
* unnecessary synchronization: user changes some data on the server,
* the sync adapter updates the contact on the phone (without the
* CALLER_IS_SYNCADAPTER flag) flag, which sets the DIRTY flag,
* which triggers a sync to bring the changes to the server.
* </td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #SYNC1}</td>
* <td>read/write</td>
* <td>Generic column provided for arbitrary use by sync adapters.
* The content provider
* stores this information on behalf of the sync adapter but does not
* interpret it in any way.
* </td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #SYNC2}</td>
* <td>read/write</td>
* <td>Generic column for use by sync adapters.
* </td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #SYNC3}</td>
* <td>read/write</td>
* <td>Generic column for use by sync adapters.
* </td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #SYNC4}</td>
* <td>read/write</td>
* <td>Generic column for use by sync adapters.
* </td>
* </tr>
* </table>
*/
public static final class RawContacts implements BaseColumns, RawContactsColumns,
ContactOptionsColumns, ContactNameColumns, SyncColumns {
/**
* This utility class cannot be instantiated
*/
private RawContacts() {
}
/**
* The content:// style URI for this table, which requests a directory of
* raw contact rows matching the selection criteria.
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "raw_contacts");
/**
* The MIME type of the results from {@link #CONTENT_URI} when a specific
* ID value is not provided, and multiple raw contacts may be returned.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/raw_contact";
/**
* The MIME type of the results when a raw contact ID is appended to {@link #CONTENT_URI},
* yielding a subdirectory of a single person.
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/raw_contact";
/**
* Aggregation mode: aggregate immediately after insert or update operation(s) are complete.
*/
public static final int AGGREGATION_MODE_DEFAULT = 0;
/**
* Aggregation mode: aggregate at the time the raw contact is inserted/updated.
* @deprecated Aggregation is synchronous, this historic value is a no-op
*/
@Deprecated
public static final int AGGREGATION_MODE_IMMEDIATE = 1;
/**
* <p>
* Aggregation mode: aggregation suspended temporarily, and is likely to be resumed later.
* Changes to the raw contact will update the associated aggregate contact but will not
* result in any change in how the contact is aggregated. Similar to
* {@link #AGGREGATION_MODE_DISABLED}, but maintains a link to the corresponding
* {@link Contacts} aggregate.
* </p>
* <p>
* This can be used to postpone aggregation until after a series of updates, for better
* performance and/or user experience.
* </p>
* <p>
* Note that changing
* {@link #AGGREGATION_MODE} from {@link #AGGREGATION_MODE_SUSPENDED} to
* {@link #AGGREGATION_MODE_DEFAULT} does not trigger an aggregation pass, but any
* subsequent
* change to the raw contact's data will.
* </p>
*/
public static final int AGGREGATION_MODE_SUSPENDED = 2;
/**
* <p>
* Aggregation mode: never aggregate this raw contact. The raw contact will not
* have a corresponding {@link Contacts} aggregate and therefore will not be included in
* {@link Contacts} query results.
* </p>
* <p>
* For example, this mode can be used for a raw contact that is marked for deletion while
* waiting for the deletion to occur on the server side.
* </p>
*
* @see #AGGREGATION_MODE_SUSPENDED
*/
public static final int AGGREGATION_MODE_DISABLED = 3;
/**
* Build a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}
* style {@link Uri} for the parent {@link android.provider.ContactsContract.Contacts}
* entry of the given {@link RawContacts} entry.
*/
public static Uri getContactLookupUri(ContentResolver resolver, Uri rawContactUri) {
// TODO: use a lighter query by joining rawcontacts with contacts in provider
final Uri dataUri = Uri.withAppendedPath(rawContactUri, Data.CONTENT_DIRECTORY);
final Cursor cursor = resolver.query(dataUri, new String[] {
RawContacts.CONTACT_ID, Contacts.LOOKUP_KEY
}, null, null, null);
Uri lookupUri = null;
try {
if (cursor != null && cursor.moveToFirst()) {
final long contactId = cursor.getLong(0);
final String lookupKey = cursor.getString(1);
return Contacts.getLookupUri(contactId, lookupKey);
}
} finally {
if (cursor != null) cursor.close();
}
return lookupUri;
}
/**
* A sub-directory of a single raw contact that contains all of its
* {@link ContactsContract.Data} rows. To access this directory
* append {@link Data#CONTENT_DIRECTORY} to the raw contact URI.
*/
public static final class Data implements BaseColumns, DataColumns {
/**
* no public constructor since this is a utility class
*/
private Data() {
}
/**
* The directory twig for this sub-table
*/
public static final String CONTENT_DIRECTORY = "data";
}
/**
* <p>
* A sub-directory of a single raw contact that contains all of its
* {@link ContactsContract.Data} rows. To access this directory append
* {@link RawContacts.Entity#CONTENT_DIRECTORY} to the raw contact URI. See
* {@link RawContactsEntity} for a stand-alone table containing the same
* data.
* </p>
* <p>
* Entity has two ID fields: {@link #_ID} for the raw contact
* and {@link #DATA_ID} for the data rows.
* Entity always contains at least one row, even if there are no
* actual data rows. In this case the {@link #DATA_ID} field will be
* null.
* </p>
* <p>
* Using Entity should be preferred to using two separate queries:
* RawContacts followed by Data. The reason is that Entity reads all
* data for a raw contact in one transaction, so there is no possibility
* of the data changing between the two queries.
*/
public static final class Entity implements BaseColumns, DataColumns {
/**
* no public constructor since this is a utility class
*/
private Entity() {
}
/**
* The directory twig for this sub-table
*/
public static final String CONTENT_DIRECTORY = "entity";
/**
* The ID of the data row. The value will be null if this raw contact has no
* data rows.
* <P>Type: INTEGER</P>
*/
public static final String DATA_ID = "data_id";
}
/**
* <p>
* A sub-directory of a single raw contact that contains all of its
* {@link ContactsContract.StreamItems} rows. To access this directory append
* {@link RawContacts.StreamItems#CONTENT_DIRECTORY} to the raw contact URI. See
* {@link ContactsContract.StreamItems} for a stand-alone table containing the
* same data.
* </p>
* <p>
* Access to the social stream through this sub-directory requires additional permissions
* beyond the read/write contact permissions required by the provider. Querying for
* social stream data requires android.permission.READ_SOCIAL_STREAM permission, and
* inserting or updating social stream items requires android.permission.WRITE_SOCIAL_STREAM
* permission.
* </p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final class StreamItems implements BaseColumns, StreamItemsColumns {
/**
* No public constructor since this is a utility class
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
private StreamItems() {
}
/**
* The directory twig for this sub-table
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTENT_DIRECTORY = "stream_items";
}
/**
* <p>
* A sub-directory of a single raw contact that represents its primary
* display photo. To access this directory append
* {@link RawContacts.DisplayPhoto#CONTENT_DIRECTORY} to the raw contact URI.
* The resulting URI represents an image file, and should be interacted with
* using ContentResolver.openAssetFileDescriptor.
* <p>
* <p>
* Note that this sub-directory also supports opening the photo as an asset file
* in write mode. Callers can create or replace the primary photo associated
* with this raw contact by opening the asset file and writing the full-size
* photo contents into it. When the file is closed, the image will be parsed,
* sized down if necessary for the full-size display photo and thumbnail
* dimensions, and stored.
* </p>
* <p>
* Usage example:
* <pre>
* public void writeDisplayPhoto(long rawContactId, byte[] photo) {
* Uri rawContactPhotoUri = Uri.withAppendedPath(
* ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
* RawContacts.DisplayPhoto.CONTENT_DIRECTORY);
* try {
* AssetFileDescriptor fd =
* getContentResolver().openAssetFileDescriptor(rawContactPhotoUri, "rw");
* OutputStream os = fd.createOutputStream();
* os.write(photo);
* os.close();
* fd.close();
* } catch (IOException e) {
* // Handle error cases.
* }
* }
* </pre>
* </p>
*/
public static final class DisplayPhoto {
/**
* No public constructor since this is a utility class
*/
private DisplayPhoto() {
}
/**
* The directory twig for this sub-table
*/
public static final String CONTENT_DIRECTORY = "display_photo";
}
/**
* TODO: javadoc
* @param cursor
* @return
*/
public static EntityIterator newEntityIterator(Cursor cursor) {
return new EntityIteratorImpl(cursor);
}
private static class EntityIteratorImpl extends CursorEntityIterator {
private static final String[] DATA_KEYS = new String[]{
Data.DATA1,
Data.DATA2,
Data.DATA3,
Data.DATA4,
Data.DATA5,
Data.DATA6,
Data.DATA7,
Data.DATA8,
Data.DATA9,
Data.DATA10,
Data.DATA11,
Data.DATA12,
Data.DATA13,
Data.DATA14,
Data.DATA15,
Data.SYNC1,
Data.SYNC2,
Data.SYNC3,
Data.SYNC4};
public EntityIteratorImpl(Cursor cursor) {
super(cursor);
}
@Override
public android.content.Entity getEntityAndIncrementCursor(Cursor cursor)
throws RemoteException {
final int columnRawContactId = cursor.getColumnIndexOrThrow(RawContacts._ID);
final long rawContactId = cursor.getLong(columnRawContactId);
// we expect the cursor is already at the row we need to read from
ContentValues cv = new ContentValues();
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_NAME);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_TYPE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, DATA_SET);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, VERSION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SOURCE_ID);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC1);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC2);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC3);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC4);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DELETED);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, CONTACT_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, STARRED);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, NAME_VERIFIED);
android.content.Entity contact = new android.content.Entity(cv);
// read data rows until the contact id changes
do {
if (rawContactId != cursor.getLong(columnRawContactId)) {
break;
}
// add the data to to the contact
cv = new ContentValues();
cv.put(Data._ID, cursor.getLong(cursor.getColumnIndexOrThrow(Entity.DATA_ID)));
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
Data.RES_PACKAGE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Data.MIMETYPE);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, Data.IS_PRIMARY);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
Data.IS_SUPER_PRIMARY);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, Data.DATA_VERSION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
CommonDataKinds.GroupMembership.GROUP_SOURCE_ID);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
Data.DATA_VERSION);
for (String key : DATA_KEYS) {
final int columnIndex = cursor.getColumnIndexOrThrow(key);
switch (cursor.getType(columnIndex)) {
case Cursor.FIELD_TYPE_NULL:
// don't put anything
break;
case Cursor.FIELD_TYPE_INTEGER:
case Cursor.FIELD_TYPE_FLOAT:
case Cursor.FIELD_TYPE_STRING:
cv.put(key, cursor.getString(columnIndex));
break;
case Cursor.FIELD_TYPE_BLOB:
cv.put(key, cursor.getBlob(columnIndex));
break;
default:
throw new IllegalStateException("Invalid or unhandled data type");
}
}
contact.addSubValue(ContactsContract.Data.CONTENT_URI, cv);
} while (cursor.moveToNext());
return contact;
}
}
}
/**
* Social status update columns.
*
* @see StatusUpdates
* @see ContactsContract.Data
*/
protected interface StatusColumns {
/**
* Contact's latest presence level.
* <P>Type: INTEGER (one of the values below)</P>
*/
public static final String PRESENCE = "mode";
/**
* @deprecated use {@link #PRESENCE}
*/
@Deprecated
public static final String PRESENCE_STATUS = PRESENCE;
/**
* An allowed value of {@link #PRESENCE}.
*/
int OFFLINE = 0;
/**
* An allowed value of {@link #PRESENCE}.
*/
int INVISIBLE = 1;
/**
* An allowed value of {@link #PRESENCE}.
*/
int AWAY = 2;
/**
* An allowed value of {@link #PRESENCE}.
*/
int IDLE = 3;
/**
* An allowed value of {@link #PRESENCE}.
*/
int DO_NOT_DISTURB = 4;
/**
* An allowed value of {@link #PRESENCE}.
*/
int AVAILABLE = 5;
/**
* Contact latest status update.
* <p>Type: TEXT</p>
*/
public static final String STATUS = "status";
/**
* @deprecated use {@link #STATUS}
*/
@Deprecated
public static final String PRESENCE_CUSTOM_STATUS = STATUS;
/**
* The absolute time in milliseconds when the latest status was inserted/updated.
* <p>Type: NUMBER</p>
*/
public static final String STATUS_TIMESTAMP = "status_ts";
/**
* The package containing resources for this status: label and icon.
* <p>Type: TEXT</p>
*/
public static final String STATUS_RES_PACKAGE = "status_res_package";
/**
* The resource ID of the label describing the source of the status update, e.g. "Google
* Talk". This resource should be scoped by the {@link #STATUS_RES_PACKAGE}.
* <p>Type: NUMBER</p>
*/
public static final String STATUS_LABEL = "status_label";
/**
* The resource ID of the icon for the source of the status update.
* This resource should be scoped by the {@link #STATUS_RES_PACKAGE}.
* <p>Type: NUMBER</p>
*/
public static final String STATUS_ICON = "status_icon";
/**
* Contact's audio/video chat capability level.
* <P>Type: INTEGER (one of the values below)</P>
*/
public static final String CHAT_CAPABILITY = "chat_capability";
/**
* An allowed flag of {@link #CHAT_CAPABILITY}. Indicates audio-chat capability (microphone
* and speaker)
*/
public static final int CAPABILITY_HAS_VOICE = 1;
/**
* An allowed flag of {@link #CHAT_CAPABILITY}. Indicates that the contact's device can
* display a video feed.
*/
public static final int CAPABILITY_HAS_VIDEO = 2;
/**
* An allowed flag of {@link #CHAT_CAPABILITY}. Indicates that the contact's device has a
* camera that can be used for video chat (e.g. a front-facing camera on a phone).
*/
public static final int CAPABILITY_HAS_CAMERA = 4;
}
/**
* <p>
* Constants for the stream_items table, which contains social stream updates from
* the user's contact list.
* </p>
* <p>
* Only a certain number of stream items will ever be stored under a given raw contact.
* Users of this API can query {@link ContactsContract.StreamItems#CONTENT_LIMIT_URI} to
* determine this limit, and should restrict the number of items inserted in any given
* transaction correspondingly. Insertion of more items beyond the limit will
* automatically lead to deletion of the oldest items, by {@link StreamItems#TIMESTAMP}.
* </p>
* <p>
* Access to the social stream through these URIs requires additional permissions beyond the
* read/write contact permissions required by the provider. Querying for social stream data
* requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating social
* stream items requires android.permission.WRITE_SOCIAL_STREAM permission.
* </p>
* <h3>Account check</h3>
* <p>
* The content URIs to the insert, update and delete operations are required to have the account
* information matching that of the owning raw contact as query parameters, namely
* {@link RawContacts#ACCOUNT_TYPE} and {@link RawContacts#ACCOUNT_NAME}.
* {@link RawContacts#DATA_SET} isn't required.
* </p>
* <h3>Operations</h3>
* <dl>
* <dt><b>Insert</b></dt>
* <dd>
* <p>Social stream updates are always associated with a raw contact. There are a couple
* of ways to insert these entries.
* <dl>
* <dt>Via the {@link RawContacts.StreamItems#CONTENT_DIRECTORY} sub-path of a raw contact:</dt>
* <dd>
* <pre>
* ContentValues values = new ContentValues();
* values.put(StreamItems.TEXT, "Breakfasted at Tiffanys");
* values.put(StreamItems.TIMESTAMP, timestamp);
* values.put(StreamItems.COMMENTS, "3 people reshared this");
* Uri.Builder builder = RawContacts.CONTENT_URI.buildUpon();
* ContentUris.appendId(builder, rawContactId);
* builder.appendEncodedPath(RawContacts.StreamItems.CONTENT_DIRECTORY);
* builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName);
* builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType);
* Uri streamItemUri = getContentResolver().insert(builder.build(), values);
* long streamItemId = ContentUris.parseId(streamItemUri);
* </pre>
* </dd>
* <dt>Via {@link StreamItems#CONTENT_URI}:</dt>
* <dd>
*<pre>
* ContentValues values = new ContentValues();
* values.put(StreamItems.RAW_CONTACT_ID, rawContactId);
* values.put(StreamItems.TEXT, "Breakfasted at Tiffanys");
* values.put(StreamItems.TIMESTAMP, timestamp);
* values.put(StreamItems.COMMENTS, "3 people reshared this");
* Uri.Builder builder = StreamItems.CONTENT_URI.buildUpon();
* builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName);
* builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType);
* Uri streamItemUri = getContentResolver().insert(builder.build(), values);
* long streamItemId = ContentUris.parseId(streamItemUri);
*</pre>
* </dd>
* </dl>
* </dd>
* </p>
* <p>
* Once a {@link StreamItems} entry has been inserted, photos associated with that
* social update can be inserted. For example, after one of the insertions above,
* photos could be added to the stream item in one of the following ways:
* <dl>
* <dt>Via a URI including the stream item ID:</dt>
* <dd>
* <pre>
* values.clear();
* values.put(StreamItemPhotos.SORT_INDEX, 1);
* values.put(StreamItemPhotos.PHOTO, photoData);
* getContentResolver().insert(Uri.withAppendedPath(
* ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
* StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), values);
* </pre>
* </dd>
* <dt>Via {@link ContactsContract.StreamItems#CONTENT_PHOTO_URI}:</dt>
* <dd>
* <pre>
* values.clear();
* values.put(StreamItemPhotos.STREAM_ITEM_ID, streamItemId);
* values.put(StreamItemPhotos.SORT_INDEX, 1);
* values.put(StreamItemPhotos.PHOTO, photoData);
* getContentResolver().insert(StreamItems.CONTENT_PHOTO_URI, values);
* </pre>
* <p>Note that this latter form allows the insertion of a stream item and its
* photos in a single transaction, by using {@link ContentProviderOperation} with
* back references to populate the stream item ID in the {@link ContentValues}.
* </dd>
* </dl>
* </p>
* </dd>
* <dt><b>Update</b></dt>
* <dd>Updates can be performed by appending the stream item ID to the
* {@link StreamItems#CONTENT_URI} URI. Only social stream entries that were
* created by the calling package can be updated.</dd>
* <dt><b>Delete</b></dt>
* <dd>Deletes can be performed by appending the stream item ID to the
* {@link StreamItems#CONTENT_URI} URI. Only social stream entries that were
* created by the calling package can be deleted.</dd>
* <dt><b>Query</b></dt>
* <dl>
* <dt>Finding all social stream updates for a given contact</dt>
* <dd>By Contact ID:
* <pre>
* Cursor c = getContentResolver().query(Uri.withAppendedPath(
* ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
* Contacts.StreamItems.CONTENT_DIRECTORY),
* null, null, null, null);
* </pre>
* </dd>
* <dd>By lookup key:
* <pre>
* Cursor c = getContentResolver().query(Contacts.CONTENT_URI.buildUpon()
* .appendPath(lookupKey)
* .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
* null, null, null, null);
* </pre>
* </dd>
* <dt>Finding all social stream updates for a given raw contact</dt>
* <dd>
* <pre>
* Cursor c = getContentResolver().query(Uri.withAppendedPath(
* ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
* RawContacts.StreamItems.CONTENT_DIRECTORY)),
* null, null, null, null);
* </pre>
* </dd>
* <dt>Querying for a specific stream item by ID</dt>
* <dd>
* <pre>
* Cursor c = getContentResolver().query(ContentUris.withAppendedId(
* StreamItems.CONTENT_URI, streamItemId),
* null, null, null, null);
* </pre>
* </dd>
* </dl>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final class StreamItems implements BaseColumns, StreamItemsColumns {
/**
* This utility class cannot be instantiated
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
private StreamItems() {
}
/**
* The content:// style URI for this table, which handles social network stream
* updates for the user's contacts.
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "stream_items");
/**
* <p>
* A content:// style URI for the photos stored in a sub-table underneath
* stream items. This is only used for inserts, and updates - queries and deletes
* for photos should be performed by appending
* {@link StreamItems.StreamItemPhotos#CONTENT_DIRECTORY} path to URIs for a
* specific stream item.
* </p>
* <p>
* When using this URI, the stream item ID for the photo(s) must be identified
* in the {@link ContentValues} passed in.
* </p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final Uri CONTENT_PHOTO_URI = Uri.withAppendedPath(CONTENT_URI, "photo");
/**
* This URI allows the caller to query for the maximum number of stream items
* that will be stored under any single raw contact.
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final Uri CONTENT_LIMIT_URI =
Uri.withAppendedPath(AUTHORITY_URI, "stream_items_limit");
/**
* The MIME type of a directory of stream items.
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item";
/**
* The MIME type of a single stream item.
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/stream_item";
/**
* Queries to {@link ContactsContract.StreamItems#CONTENT_LIMIT_URI} will
* contain this column, with the value indicating the maximum number of
* stream items that will be stored under any single raw contact.
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String MAX_ITEMS = "max_items";
/**
* <p>
* A sub-directory of a single stream item entry that contains all of its
* photo rows. To access this
* directory append {@link StreamItems.StreamItemPhotos#CONTENT_DIRECTORY} to
* an individual stream item URI.
* </p>
* <p>
* Access to social stream photos requires additional permissions beyond the read/write
* contact permissions required by the provider. Querying for social stream photos
* requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating
* social stream photos requires android.permission.WRITE_SOCIAL_STREAM permission.
* </p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final class StreamItemPhotos
implements BaseColumns, StreamItemPhotosColumns {
/**
* No public constructor since this is a utility class
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
private StreamItemPhotos() {
}
/**
* The directory twig for this sub-table
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTENT_DIRECTORY = "photo";
/**
* The MIME type of a directory of stream item photos.
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item_photo";
/**
* The MIME type of a single stream item photo.
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/stream_item_photo";
}
}
/**
* Columns in the StreamItems table.
*
* @see ContactsContract.StreamItems
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
protected interface StreamItemsColumns {
/**
* A reference to the {@link android.provider.ContactsContract.Contacts#_ID}
* that this stream item belongs to.
*
* <p>Type: INTEGER</p>
* <p>read-only</p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTACT_ID = "contact_id";
/**
* A reference to the {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY}
* that this stream item belongs to.
*
* <p>Type: TEXT</p>
* <p>read-only</p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String CONTACT_LOOKUP_KEY = "contact_lookup";
/**
* A reference to the {@link RawContacts#_ID}
* that this stream item belongs to.
* <p>Type: INTEGER</p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String RAW_CONTACT_ID = "raw_contact_id";
/**
* The package name to use when creating {@link Resources} objects for
* this stream item. This value is only designed for use when building
* user interfaces, and should not be used to infer the owner.
* <P>Type: TEXT</P>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String RES_PACKAGE = "res_package";
/**
* The account type to which the raw_contact of this item is associated. See
* {@link RawContacts#ACCOUNT_TYPE}
*
* <p>Type: TEXT</p>
* <p>read-only</p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String ACCOUNT_TYPE = "account_type";
/**
* The account name to which the raw_contact of this item is associated. See
* {@link RawContacts#ACCOUNT_NAME}
*
* <p>Type: TEXT</p>
* <p>read-only</p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String ACCOUNT_NAME = "account_name";
/**
* The data set within the account that the raw_contact of this row belongs to. This allows
* multiple sync adapters for the same account type to distinguish between
* each others' data.
* {@link RawContacts#DATA_SET}
*
* <P>Type: TEXT</P>
* <p>read-only</p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String DATA_SET = "data_set";
/**
* The source_id of the raw_contact that this row belongs to.
* {@link RawContacts#SOURCE_ID}
*
* <P>Type: TEXT</P>
* <p>read-only</p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String RAW_CONTACT_SOURCE_ID = "raw_contact_source_id";
/**
* The resource name of the icon for the source of the stream item.
* This resource should be scoped by the {@link #RES_PACKAGE}. As this can only reference
* drawables, the "@drawable/" prefix must be omitted.
* <P>Type: TEXT</P>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String RES_ICON = "icon";
/**
* The resource name of the label describing the source of the status update, e.g. "Google
* Talk". This resource should be scoped by the {@link #RES_PACKAGE}. As this can only
* reference strings, the "@string/" prefix must be omitted.
* <p>Type: TEXT</p>
*
* @deprecated - Do not use. This will not be supported in the future. In the future,
* cursors returned from related queries will be empty.
*/
@Deprecated
public static final String RES_LABEL = "label";
/**
* <P>
* The main textual contents of the item. Typically this is content
* that was posted by the source of this stream item, but it can also
* be a textual representation of an action (e.g. â |