레이블이 Android인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Android인 게시물을 표시합니다. 모든 게시물 표시

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월 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.

2013년 6월 26일 수요일

Context

안드로이드 레퍼런스의 설명만으로 정리가 되어 버린다.
Interface to global information about an application environment.
This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.
왜 진작 찾아 보지 않았는지.. Activity든 Service든 Context를 간접적으로 상속받고 있다.

ContentObserver

안드로이드 레퍼런스에서는 다음과 같이 설명한다.
Receives call backs for changes to content. Must be implemented by objects which are added to a ContentObservable.
Latin IME의 소스에서 사용되는 부분을 발췌하면 다음과 같다.
com.android.inputmethod.latin.UserBinaryDictionary의 생성자에서 아래 코드를 발견할 수 있다.
        ContentResolver cres = context.getContentResolver();

        mObserver = new ContentObserver(null) {
            @Override
            public void onChange(final boolean self) {
                // This hook is deprecated as of API level 16, but should still be supported for
                // cases where the IME is running on an older version of the platform.
                onChange(self, null);
            }
            // The following hook is only available as of API level 16, and as such it will only
            // work on JellyBean+ devices. On older versions of the platform, the hook
            // above will be called instead.
            @Override
            public void onChange(final boolean self, final Uri uri) {
                setRequiresReload(true);
                // We want to report back to Latin IME in case the user just entered the word.
                // If the user changed the word in the dialog box, then we want to replace
                // what was entered in the text field.
                if (null == uri || !(context instanceof LatinIME)) return;
                final long changedRowId = ContentUris.parseId(uri);
                if (-1 == changedRowId) return; // Unknown content... Not sure why we're here
                final String changedWord = getChangedWordForUri(uri);
                ((LatinIME)context).onWordAddedToUserDictionary(changedWord);
            }
        };
        cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
이코드만 보면, getContentResolver에서 ContentResolver를 가져와서 Observer를 등록하면 된다.
종료 때는 위 소스에는 빠져있지만 unregisterContentObserver를 사용해서 해제하면 된다.
전혀 복잡할 내용도 없고 간결한데 뭔가 답답한 이유는,
context, Uri 에 대한 개념이 부족해서이리라.

contextUri를 좀 더 확인 후 여기에도 정리해 두겠다.


== 추가 내용 ==
Uri는 특별한게 없고, 예상하던 Uniform Resource Identifiers가 맞다.
어렵게 생각할 필요 없었다.
Words.CONTENT_URI는 android.jar에 포함되어 있었다. 이 값이 궁금해 진행이 늦었다.
latin IME소스에 있는 줄 알고 계속 Find usages 호출해서 헤메고 있었는데. Navigation bar를 잘 봤어야지..
어쟀든 값은
public static final Uri CONTENT_URI = Uri.parse("content://user_dictionary/words");

2010년 8월 7일 토요일

Android 에뮬레이터 실행시 오류

eclipse 의 Android SDK and AVD Manager 로 AVD 를 만들고 에뮬레이터를 실행하려는데 다음과 같은 오류가 발생하는 경우가 있다.

emulator: ERROR: bad config: virtual device directory lacks config.ini

컴퓨터 사용자 명이 한글로 되어 있는 경우에 발생한다.

ndroid SDK and AVD Manager 로 AVD 를 만들게 되면
C:\Documents and Settings\[사용자명]\.android\avd
폴더 내에 AVD 이름으로 된 ini 파일이 있고 그 파일에 avd 정보가 들어있다.
예를들어 Google2.2 라는 이름으로 AVD 를 만들었을 경우
C:\Documents and Settings\[사용자명]\.android\avd\Google2.2.ini
C:\Documents and Settings\[사용자명]\.android\avd\Google2.2\[상세한 설정파일]
로 파일들이 만들어진다.

Google2.2.ini 파일은

target=Google Inc.:Google APIs:8
path=C:\Documents and Settings\[사용자명]\.android\avd\Google2.2
와 같은 정보가 들어 있을 뿐이다.

여기에서 Google2.2.ini 파일은 읽었으나 C:\Documents and Settings\[사용자명]\.android\avd\Google2.2 폴더는 한글때문에 찾지 못하는 것으로 보인다.

SDK 가 설치된 폴더의 tools 폴더의 android.bat 파일을 이용해서 avd 를 특정 폴더에 만들 수 있다.

android create avd -n [AVD이름] -p [경로] -t [타겟]

타겟은 android list target 으로 확인 할 수있다.

위의 Google2.2 를 C:\avd 에 만드는 경우

android create avd -n Google2.2 -p C:\avd -t 10

으로 할 수 있다.