2013년 7월 15일 월요일

중국어 입력기

개발자의 입장에서 보자면
한글도 Latin계열의 문자들에 비해, 자소들을 조합해서 음절을 만들어야 하니 어렵다고 볼 수 있겠으나,
일본어, 중국어는 그 이상이다.
일본어는 차치하고 중국어 입력기만 한 번 정리해보자

중국 본토에서 사용하느냐, 홍콩, 대만 등지에서 사용하거나에 따라 사전에 포함되어야 하는 글자다 달라진다.
또한 상당한 수의 낱자들 중에서 입력할 글자를 골라야 하기에 추천 기능이 필수적이다.

중국어의 여러 입력법들은 사용자가 입력하는 Key가 다를 뿐이지 기본 동작은 같다.
사용자가 Key를 입력하면, Key에 맞는 낱자 혹은 단어를 사전에서 찾아 목록을 만들고, 사용자는 그 중 하나를 선택해 입력하는 방식이다.
다음은 주로 사용되는 입력 방식들이다.

Pinyin 입력법

  • 병음이 검색의 Key가 되며, 병음은 영문 키보드로 입력한다.
  • 각 글자의 병음 첫 알파벳만 입력해도 해당 단어가 추천되어 입력 가능하다.
  • 输入法의 경우 sherufa로 전체 병음을 모두 입력해도 되지만 한자의 병음에 해당하는 첫 알파벳(SRF)만 입력하면 병음에 해당하는 한자가 히트된다.
  • 대부분의 중국 본토(간체 입력시)에서 사용하는 입력법이다.
  • Sogou 입력기

Cangjie 입력법

  • wiki::Cangjie input method
  • 창제 코드가 각인된 키보드를 통해 창제 코드를 입력한다.
  • 예들 들면 車를 입력하기 위해 十 田 十 를 입력해야 한다.
  • 따라서 사전에 포함된 낱자들의 창제코드가 필요해진다.
GroupKeyNamePrimary meaning
Philosophical groupA日 sun日, 曰, 90° rotated 日 (as in 巴)
B月 moonthe top four strokes of 目, 冂, 爫, 冖, the top and top-left part of 炙, 然, and 祭, the top-left four strokes of 豹 and 貓, and the top four strokes of 骨
C金 golditself, 丷, 八, and the penultimate two strokes of 四 and 匹
D木 wooditself, the first two strokes of 寸 and 才, the first two strokes of 也 and 皮
E水 water氵, the last five strokes of 暴 and 康, 又
F火 firethe shape 小, 灬, the first three strokes in 當 and 光
G土 earth
Stroke groupH竹 bambooThe slant and short slant, the Kangxi radical 竹, namely the first four strokes in 笨 and 節
I戈 weaponThe dot, the first three strokes in 床 and 庫, and the shape 厶
J十 tenThe cross shape and the shape 宀
K大 bigThe X shape, including 乂 and the first two strokes of 右, as well as 疒
L中 centreThe vertical stroke, as well as 衤 and the first four strokes of 書 and 盡
M一 oneThe horizontal stroke, as well as the final stroke of 孑 and 刁, the shape 厂, and the shape 工
N弓 bowThe crossbow and the hook
Body parts groupO人 personThe dismemberment, the Kangxi radical 人, the first two strokes of 丘 and 乓, the first two strokes of 知, 攻, and 氣, and the final two strokes of 兆
P心 heartThe Kangxi radical 忄, the second stroke in 心, the last four strokes in 恭, 慕, and 忝, the shape 匕, the shape 七, the penultimate two strokes in 代, and the shape 勹
Q手 handThe Kangxi radical 手
R口 mouthThe Kangxi radical 口
Character shapes groupS尸 corpse匚, the first two strokes of 己, the first stroke of 司 and 刀, the third stroke of 成 and 豕, the first four strokes of 長 and 髟
T廿 twentyTwo vertical strokes connected by a horizontal stroke; the Kangxi radical 艸 when written as 艹 (whether the horizontal stroke is connected or broken)
U山 mountainThree-sided enclosure with an opening on the top
V女 womanA hook to the right, a V shape, the last three strokes in 艮, 衣, and 長
W田 fieldItself, as well as any four-sided enclosure with something inside it, including the first two strokes in 母 and 毋
Y卜 fortune tellingThe 卜 shape and rotated forms, the shape 辶, the first two strokes in 斗
Collision/Difficult key*X重/難 collision/difficult(1) disambiguation of Cangjie code decomposition collisions, (2) code for a "difficult-to-decompose" part
Special character key*Z(See note)Auxiliary code used for entering special characters (no meaning on its own). In most cases, this key combined with other keys will produce Chinese punctuations (such as 。,、,「 」,『 』).
Note: Some variants use Z as a collision key instead of X. In those systems, Z has the name "collision" (重) and X has the name "difficult" (難); but the use of Z as a collision key is neither in the original Cangjie nor used in the current mainstream implementations. In other variants, Z may have the name "user-defined" (造) or some other name.

Zhuyin 입력법

  • Bopomofo 입력법이라고도 한다.
  • 법체 입력시 사용되는 입력법이다.
  • 주음 기호가 각인된 키보드를 통해 발음을 입력한다.
  • 맞지 않는 주음 기호 조합인 경우 입력이 사전에 차단된다.
  • wiki::Zhuyin table로 주음 기호를 병음기호로 변경가능 해 보인다.
  • Pynyin입력 모듈을 재사용 할 수 있겠다.

획 입력법

  • 필기구로 글자를 쓸 때처럼 5가지의 획을 순서대로 입력한다.
  • 해당 글자의 모든 획을 다 입력하지 않아도 글자는 추천된다.
  • 순서대로 입력하지 않으면 추천되지 않는다.
  • 5 종류의 획을 key가 아닌 drawing으로 입력 받으면 handwriting으로 보여질 것 같다.

2013년 7월 9일 화요일

옵저버 패턴

옵저버 패턴에서는 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고
자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의합니다.
신문 구독으로 간단한 예를들 수 있다.
신문사는 새로운 신문을 발행한다. 신문을 구독 중인 고객들에게는 새 신문이 배달되고, 고객들은 자동으로 구독해지, 신청을 언제든지 할 수 있다.
신문사는 구독 중인 고객들에게 신문을 배달하는 순서는 신경쓰지 않는다.

간단한 기상 스테이션을 Observer 패턴으로 구현해보자.
다음과 같이 subject, observer 인터페이스를 만들어 간단히 구현 가능하다.

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

public interface Observer {
    public void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
    public void display();
}
신문사에 해당하는 Subject 인터페이스와 고객인 Observer 인터페이스, 기상 정보를 표현할 DisplayElement 인터페이스로 구성되어 있다.
public class WeatherData implements Subject {
    private final ArrayList<Observer> observers;
    private float pressure;
    private float humidity;
    private float temperature;

    public WeatherData() {
        this.observers = new ArrayList<Observer>();
    }

    public void setMeasurements(int temperature, int humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    private void measurementsChanged() {
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for(Observer o : observers) {
            o.update(this.temperature, this.humidity, this.pressure);
        }
    }
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private final Subject weatherData;
    private float humidity;
    private float temperature;

    public CurrentConditionsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Current conditions: " + this.temperature + "F degrees and " + this.humidity + "% humidity");
    }

    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        display();
    }
}

WeatherData 클래스는 Observer들을 관리할 Observer 리스트를 가지고 있으며, registerObserver를 통해 등록된 Observer들에게 기상정보가 변경될 때 update메세지를 보낸다.

Observer인 CurrentConditionsDisplay 클래스는 weatherData의 registerObsever를 통해 Subject 클래스로 자신의 참조를 전달하고, Subject에서 update를 호줄하면 예정된 동작을 실행하게 된다.
현재 클래스에서는 생성자에서 register를 했으나 원할 때 remove, register 가능하다.

자바에서 Observer 패턴을 위한 기능이 있다.

java.util.Observable;
java.util.Observer;
위의 WeatherData와 CurrentConditionsDisplay를 아래와 같이 변경가능하다.
public class WeatherData extends Observable {
    private float pressure;
    private float humidity;
    private float temperature;

    public void setMeasurements(int temperature, int humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {

        return humidity;
    }

    public float getPressure() {

        return pressure;
    }

    private void measurementsChanged() {
        setChanged();
        notifyObservers();
    }
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private final Observable weatherData;
    private float humidity;
    private float temperature;

    public CurrentConditionsDisplay(Observable weatherData) {
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Current conditions: " + this.temperature + "F degrees and " + this.humidity + "% humidity");
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) o;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
}

주의할 점은 Observable이 인터페이스가 아니라 Class라는 점이다. 따라서 Observer를 위한 자료구조를 만들 필요가 없으며, addObserver/deleteObserver 등의 메소드가 이미 구현되어 있다.
implements가 아니라 extends해야 하기 때문에 사용에 제약이 있을 수 있다.
setChanged()를 호출한 후 notifyObservers()를 호출하면 등록된 Observer들의 update가 호출된다.

Observer들은 update(Observable, Object)를 통해 메세지를 받는다.
Observable은 update를 호출한 객체를 전달하고, Object에는 Observable에서 notifyObservers(Object)로 넘겨준 param이 전달된다.(notifyObservers()는 notifyObservers(null)과 같다.)
위 코드에서 처럼 Observable의 getter를 통해 데이터를 가져와도 되고(pull), notifyObservers(Object)로 넘겨줘도 된다.(push)


참고도서 :

2013년 7월 3일 수요일

PrivateCommand란?

reference를 보자
http://developer.android.com/reference/android/view/inputmethod/InputMethodSession.html

public abstract void appPrivateCommand (String action, Bundle data)
Process a private command sent from the application to the input method. This can be used to provide domain-specific features that are only known between certain input methods and their clients.
어플리케이션에서 InputMethod로 보내는 private command이다. action에는 command의 이름을 넣고 Bundle에 데이터를 넣어 전달한다. action은 단지 String일 뿐이므로 conflicting에 주의해서 만들도록 한다.
InputMethod에서는 InputMethodService.onAppPrivateCommand(String, Bundle)를 통해 받을 수 있다.

반대로 InputMethod에서 editor로 private command를 보낼 때는 InputConnection의

public abstract boolean performPrivateCommand (String action, Bundle data)
를 사용한다.

Bundle(A mapping from String values to various Parcelable types.)은 대부분의 타입들을 지원하며, key를 통해 put/get을 사용한다.

PendingIntent란?

다음 블로그에 정리가 잘 되어 있다.
http://huewu.blog.me/110084228131
간략히 하면,
Intent를 직접 보내지 않고 다른 클래스에게 Intent를 위임해주기 위한 클래스 정도 되겠다.
Notification Bar와 상호작용하는 어플리케이션을 작성할 때 널리 사용된다.

http://developer.android.com/reference/android/app/PendingIntent.html를 보면
A description of an Intent and target action to perform with it.라고 한다.
다음의 메소드들로 PendingIntent의 instance를 만들 수 있다.

LatinIME에서 다음과 같은 코드가 발견된다.

    /**
     * Arrange for the UploaderService to be run on a regular basis.
     *
     * Any existing scheduled invocation of UploaderService is removed and rescheduled.  This may
     * cause problems if this method is called often and frequent updates are required, but since
     * the user will likely be sleeping at some point, if the interval is less that the expected
     * sleep duration and this method is not called during that time, the service should be invoked
     * at some point.
     */
    public static void scheduleUploadingService(Context context) {
        final Intent intent = new Intent(context, UploaderService.class);
        final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
        final AlarmManager manager =
                (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        manager.cancel(pendingIntent);
        manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent);
    }

getService를 통해 PendingIntent의 instance를 만들고 AlarmManager에 등록한다.
각 method의 parameters를 간단히 살펴보자

public static PendingIntent getService (Context context, int requestCode, Intent intent, int flags)
  • context : The Context in which this PendingIntent should start the service.
  • requestCode : Private request code for the sender (currently not used).
  • intent : An Intent describing the service to be started.
  • flags : May be FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT, or any of the flags as supported by Intent.fillIn() to control which unspecified parts of the intent that can be supplied when the actual send happens.
public void setInexactRepeating (int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)
  • type : One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP}, RTC or RTC_WAKEUP.
  • triggerAtMillis : time in milliseconds that the alarm should first go off, using the appropriate clock (depending on the alarm type). This is inexact: the alarm will not fire before this time, but there may be a delay of almost an entire alarm interval before the first invocation of the alarm.
  • intervalMillis : interval in milliseconds between subsequent repeats of the alarm. If this is one of INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY then the alarm will be phase-aligned with other alarms to reduce the number of wakeups. Otherwise, the alarm will be set as though the application had called setRepeating(int, long, long, PendingIntent).
  • operation : Action to perform when the alarm goes off; typically comes from IntentSender.getBroadcast().

2013년 7월 2일 화요일

Content Providers 정리

http://developer.android.com/guide/topics/providers/content-provider-basics.html
페이지를 보면서 아래에 내용을 요약 정리 해 둔다.

Content Provider Basics

Together, providers and provider clients offer a consistent, standard interface to data that also handles inter-process communication and secure data access.

Overview


A content provider presents data to external applications as one or more tables that are similar to the tables found in a relational database. A row represents an instance of some type of data the provider collects, and each column in the row represents an individual piece of data collected for an instance.

Accessing a provider

An application accesses the data from a content provider with a ContentResolver client object. This object has methods that call identically-named methods in the provider object, an instance of one of the concrete subclasses of ContentProvider. The ContentResolver methods provide the basic "CRUD" (create, retrieve, update, and delete) functions of persistent storage.
Note:To access a provider, your application usually has to request specific permissions in its manifest file. This is described in more detail in the section Content Provider Permissions

Content URIs

A content URI is a URI that identifies data in a provider. Content URIs include the symbolic name of the entire provider (its authority) and a name that points to a table (a path). When you call a client method to access a table in a provider, the content URI for the table is one of the arguments.
content://user_dictionary/words

Retrieving Data from the Provider


Requesting read access permission

To retrieve data from a provider, your application needs read access permission for the provider. You can't request this permission at run-time; instead, you have to specify that you need this permission in your manifest, using the element and the exact permission name defined by the provider.

Constructing the query

In the next snippet, if the user doesn't enter a word, the selection clause is set to null, and the query returns all the words in the provider. If the user enters a word, the selection clause is set to UserDictionary.Words.WORD + " = ?" and the first element of selection arguments array is set to the word the user enters.
/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] mSelectionArgs = {""};

// Gets a word from the UI
mSearchString = mSearchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input.

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(mSearchString)) {
    // Setting the selection clause to null will return all words
    mSelectionClause = null;
    mSelectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered.
    mSelectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments.
    mSelectionArgs[0] = mSearchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    mProjection,                       // The columns to return for each row
    mSelectionClause                   // Either null, or the word the user entered
    mSelectionArgs,                    // Either empty, or the string the user entered
    mSortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
     * an error. You may want to offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}
This query is analogous to the SQL statement:
SELECT _ID, word, locale FROM words WHERE word = &ltuserinput&gt ORDER BY word ASC;

Protecting against malicious input

If the data managed by the content provider is in an SQL database, including external untrusted data into raw SQL statements can lead to SQL injection.
To avoid this problem, use a selection clause that uses ? as a replaceable parameter and a separate array of selection arguments. When you do this, the user input is bound directly to the query rather than being interpreted as part of an SQL statement. Because it's not treated as SQL, the user input can't inject malicious SQL.

Displaying query results

The ContentResolver.query() client method always returns a Cursor containing the columns specified by the query's projection for the rows that match the query's selection criteria. A Cursor object provides random read access to the rows and columns it contains. Using Cursor methods, you can iterate over the rows in the results, determine the data type of each column, get the data out of a column, and examine other properties of the results. Some Cursor implementations automatically update the object when the provider's data changes, or trigger methods in an observer object when the Cursor changes, or both.
The following snippet continues the code from the previous snippet. It creates a SimpleCursorAdapter object containing the Cursor retrieved by the query, and sets this object to be the adapter for a ListView:
// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    mWordListColumns,                      // A string array of column names in the cursor
    mWordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);

Getting data from query results

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers may throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column.
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception.
}
Cursor implementations contain several "get" methods for retrieving different types of data from the object. For example, the previous snippet uses getString(). They also have a getType() method that returns a value indicating the data type of the column.

Content Provider Permissions


To get the permissions needed to access a provider, an application requests them with a element in its manifest file. When the Android Package Manager installs the application, a user must approve all of the permissions the application requests. If the user approves all of them, Package Manager continues the installation; if the user doesn't approve them, Package Manager aborts the installation.

Inserting, Updating, and Deleting Data


Inserting data

To insert data into a provider, you call the ContentResolver.insert() method. This method inserts a new row into the provider and returns a content URI for that row. This snippet shows how to insert a new word into the User Dictionary Provider:
// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;

...

// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);
The data for the new row goes into a single ContentValues object, which is similar in form to a one-row cursor. The columns in this object don't need to have the same data type, and if you don't want to specify a value at all, you can set a column to null using ContentValues.putNull().
The snippet doesn't add the _ID column, because this column is maintained automatically. The provider assigns a unique value of _ID to every row that is added. Providers usually use this value as the table's primary key.
The content URI returned in newUri identifies the newly-added row, with the following format:
content://user_dictionary/words/&ltid_value&gt

Updating data

To update a row, you use a ContentValues object with the updated values just as you do with an insertion, and selection criteria just as you do with a query. The client method you use is ContentResolver.update(). You only need to add values to the ContentValues object for columns you're updating. If you want to clear the contents of a column, set the value to null.
The following snippet changes all the rows whose locale has the language "en" to a have a locale of null. The return value is the number of rows that were updated:
// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] mSelectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Deleting data

Deleting rows is similar to retrieving row data: you specify selection criteria for the rows you want to delete and the client method returns the number of deleted rows. The following snippet deletes rows whose appid matches "user". The method returns the number of deleted rows.
// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;

...

// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Provider Data Types

Content providers can offer many different data types. The User Dictionary Provider offers only text, but providers can also offer the following formats:

  • integer
  • long integer (long)
  • floating point
  • long floating point (double)

Another data type that providers often use is Binary Large OBject (BLOB) implemented as a 64KB byte array. You can see the available data types by looking at the Cursor class "get" methods.

Alternative Forms of Provider Access

Batch access

Batch access to a provider is useful for inserting a large number of rows, or for inserting rows in multiple tables in the same method call, or in general for performing a set of operations across process boundaries as a transaction (an atomic operation).

To access a provider in "batch mode", you create an array of ContentProviderOperation objects and then dispatch them to a content provider with ContentResolver.applyBatch(). You pass the content provider's authority to this method, rather than a particular content URI. This allows each ContentProviderOperation object in the array to work against a different table. A call to ContentResolver.applyBatch() returns an array of results.

        // Prepare contact creation request
        //
        // Note: We use RawContacts because this data must be associated with a particular account.
        //       The system will aggregate this with any other data for this contact and create a
        //       coresponding entry in the ContactsContract.Contacts provider for us.
        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName())
                .build());
        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)
                .build());
        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)
                .build());
        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Email.DATA, email)
                .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)
                .build());

        // Ask the Contact provider to create a new contact
        Log.i(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
                mSelectedAccount.getType() + ")");
        Log.i(TAG,"Creating contact: " + name);
        try {
            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (Exception e) {
            // Display warning
            Context ctx = getApplicationContext();
            CharSequence txt = getString(R.string.contactCreationFailure);
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(ctx, txt, duration);
            toast.show();

            // Log exception
            Log.e(TAG, "Exceptoin encoutered while inserting contact: " + e);
        }

Asynchronous queries

You should do queries in a separate thread. One way to do this is to use a CursorLoader object. The examples in the Loaders guide demonstrate how to do this

Data access via intents

Although you can't send an intent directly to a provider, you can send an intent to the provider's application, which is usually the best-equipped to modify the provider's data.

Getting access with temporary permissions

You can access data in a content provider, even if you don't have the proper access permissions, by sending an intent to an application that does have the permissions and receiving back a result intent containing "URI" permissions. These are permissions for a specific content URI that last until the activity that receives them is finished. The application that has permanent permissions grants temporary permissions by setting a flag in the result intent:

  • Read permission: FLAG_GRANT_READ_URI_PERMISSION
  • Write permission: FLAG_GRANT_WRITE_URI_PERMISSION

For example, you can retrieve data for a contact in the Contacts Provider, even if you don't have the READ_CONTACTS permission. You might want to do this in an application that sends e-greetings to a contact on his or her birthday. Instead of requesting READ_CONTACTS, which gives you access to all of the user's contacts and all of their information, you prefer to let the user control which contacts are used by your application. To do this, you use the following process:

  1. Your application sends an intent containing the action ACTION_PICK and the "contacts" MIME type CONTENT_ITEM_TYPE, using the method startActivityForResult().
  2. Because this intent matches the intent filter for the People app's "selection" activity, the activity will come to the foreground.
  3. In the selection activity, the user selects a contact to update. When this happens, the selection activity calls setResult(resultcode, intent) to set up a intent to give back to your application. The intent contains the content URI of the contact the user selected, and the "extras" flags FLAG_GRANT_READ_URI_PERMISSION. These flags grant URI permission to your app to read data for the contact pointed to by the content URI. The selection activity then calls finish() to return control to your application.
  4. Your activity returns to the foreground, and the system calls your activity's onActivityResult() method. This method receives the result intent created by the selection activity in the People app.
  5. With the content URI from the result intent, you can read the contact's data from the Contacts Provider, even though you didn't request permanent read access permission to the provider in your manifest. You can then get the contact's birthday information or his or her email address and then send the e-greeting.

Using another application

A simple way to allow the user to modify data to which you don't have access permissions is to activate an application that has permissions and let the user do the work there.

For example, the Calendar application accepts an ACTION_INSERT intent, which allows you to activate the application's insert UI. You can pass "extras" data in this intent, which the application uses to pre-populate the UI. Because recurring events have a complex syntax, the preferred way of inserting events into the Calendar Provider is to activate the Calendar app with an ACTION_INSERT and then let the user insert the event there.

Contract Classes


A contract class defines constants that help applications work with the content URIs, column names, intent actions, and other features of a content provider. Contract classes are not included automatically with a provider; the provider's developer has to define them and then make them available to other developers. Many of the providers included with the Android platform have corresponding contract classes in the package android.provider.

MIME Type Reference


Content providers can return standard MIME media types, or custom MIME type strings, or both.

For example, the well-known MIME type text/html has the text type and the html subtype. If the provider returns this type for a URI, it means that a query using that URI will return text containing HTML tags.

Custom MIME type strings, also called "vendor-specific" MIME types, have more complex type and subtype values. The type value is always

vnd.android.cursor.dir

for multiple rows, or

vnd.android.cursor.item

for a single row.