Home > ケータイ

ケータイ Archive

[Android] オリジナルの ContentProvider を作成する

前回は ContentProvider クラスを使って コンタクトリスト から電話帳データ を取得してみました。
今回はオリジナルの ContentProvider を作ってみようと思います。
ContentProvider クラスを使い実装することで、他のアプリケーションからもデータを利用することが可能となります。

データへのアクセスは、ContentURI という URI を用い、

という形式でアクセスすることになります。
A :固定の プリフィックス
B :Authority パート。一意であることを保証する為、ContentProvider のパッケージ名を含んだクラス名とします。
AndroidManifest.xml の タグの android:authorities 属性と対応します。
C :ContentProvider が操作するデータの種類を示します。
D :データのIDを示します。省略すると全てのレコードを取得します。
となっています。

今回はGoogle が提供しているチュートリアルで作ったNotepadv3 アプリを改造します。
Notepadv3ではデータの保存に SQLite を使っていますが、これを ContentProvider を使うように変更してみましょう。

Notepadv3 プロジェクトをコピーして Notepadv4 プロジェクトを作成

既存のものをディレクトリ毎複製したあと "v3" となっている部分を "v4" に書き換えて "create project from existing source" を選択してプロジェクト作成しました。 この時点で、エミュレータ で動作させてちゃんと表示されればOK。

NotesProvider クラスで操作するデータを表現するクラスを作成

Provider を作る前に、データを表現する為の NotePad クラスを作成しておきましょう。 Notes は、DB 上では _id,title,body という3つのカラムを持ちます。

com.google.android.demo.provider パッケージを新しく作り、そこに以下のクラスを作ります。
クラス内には、データのパスを示す ContentURI やカラムを示す 定数を指定します。
BaseColumns を継承することにより、_id にあたる部分は省略できます。

package com.google.android.demo.provider;

import android.net.ContentURI;
import android.provider.BaseColumns;

public final class NotePad {
	public static final class Notes implements BaseColumns {
        /**
         * このデータへアクセスする為の URI
         */
        public static final ContentURI CONTENT_URI
                = ContentURI.create("content://com.google.android.demo.provider.NotePad/notes");

        /**
         * デフォルトのソート順
         */
        public static final String DEFAULT_SORT_ORDER = "_id DESC";

        /**
         * タイトル
         */
        public static final String TITLE = "title";

        /**
         * ノート
         */
        public static final String BODY = "body";
	}
}

ContentProvider クラスを拡張して、NotesProvider クラスを作成

これまでデータの保存に利用していた DBHelper の代わりになる、NotesProvider クラスを作ります。

com.google.android.demo.notepad4 パッケージ内に NotesProvider クラスを作りましょう。
下の図のように、ContentProvider クラスを親クラスにします。

ContentProvider を継承

生成された NotesProvider クラスのソースを見てみると、オーバーライドしなくてはいけない関数がいくつかるようです。

public int delete(ContentURI uri, String selection, String[] selectionArgs)
public String getType(ContentURI uri) 
public ContentURI insert(ContentURI uri, ContentValues values) 
public boolean onCreate() 
public Cursor query(ContentURI uri, String[] projection, String selection,
			String[] selectionArgs, String groupBy, String having,
			String sortOrder) 
public int update(ContentURI uri, ContentValues values, String selection,
			String[] selectionArgs) 

onCreate :この Provider が生成された時に実行されるメソッドです。
query :データを取得する為のメソッドです。
getType :メソッドは、この Provider で取得できる ContentURI の META タイプを返します。
insert,update,delete :それぞれ データの新規作成時、更新、削除時のメソッドです。
これらの実装は後述します。

DBへのアクセス方法を変更

これまで使っていた DBHelper クラスから、必要な処理を移植します。 まずは、インスタンスが生成された時の初期化処理です。 DBHelper クラスでは、
public DBHelper(Context ctx) {
    db = ctx.openDatabase(DATABASE_NAME, null);
とあるように、コンストラクタの中から DB へアクセスする為のインスタンスが取得できました。 ContentProvider では、ContentProviderDatabaseHelper を拡張したクラスを使ってDB へのアクセスを行います。
/**
 * DB アクセス用のHelper クラス
 * @author hal
 */
private static class DatabaseHelper extends ContentProviderDatabaseHelper {
    /**
     * テーブルのCreate文。Helper が最初に生成された際に発行される
     */ 
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table notes (_id integer primary key autoincrement, "
                + "title text not null, body text not null);");
    }
	/**
	 * アップグレード時に実行される。
	 */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS notes");
        onCreate(db);
    }
}
Notepadv3 では、Database が存在していない場合に Create Database 文を発行していましたが、ContentProviderDatabaseHelper では自動でやってくれるので必要ありません。

残りのメソッドを実装

query,insert,updateなどを実装します。 流れとしては、ContentURIParser クラスの URL_MATCHER を使って渡されてきた URI をチェックし、onCreate で生成した mDB にクエリを発行しています。 query() で使っている QueryBuilder は、 DB へ発行するクエリを生成する為のクラスです。 getType() メソッドは、この Provider を利用して取得できるデータの METAタイプを返します。 NotesProvider クラス全体は以下のようになります。
package com.google.android.demo.notepad4;

import java.util.HashMap;

import com.google.android.demo.provider.NotePad;
import android.content.ContentProvider;
import android.content.ContentProviderDatabaseHelper;
import android.content.ContentURIParser;
import android.content.ContentValues;
import android.content.QueryBuilder;
import android.content.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.ContentURI;
import android.provider.BaseColumns;
import android.text.TextUtils;

public class NotesProvider extends ContentProvider {
	private SQLiteDatabase mDB;
    // URI のパース用
	private static final ContentURIParser URL_MATCHER;
    /**
	 * DB アクセス用のHelper クラス
	 * @author hal
	 */
    private static class DatabaseHelper extends ContentProviderDatabaseHelper {
    	/**
    	 * テーブルのCreate文。Helper が最初に生成された際に発行される
    	 */ 
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("create table notes (_id integer primary key autoincrement, "
                    + "title text not null, body text not null);");
        }
		/**
		 * アップグレード時。
		 */
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS notes");
            onCreate(db);
        }
    }

	// DB名
    private static final String DATABASE_NAME = "notepad.db";
	// テーブル名
    private static final String DATABASE_TABLE = "notes";
	// DB バージョン
    private static final int DATABASE_VERSION = 1;

    private static final int NOTES = 1;
    private static final int NOTE_ID = 2;
    private static final HashMap<String, String> NOTES_LIST_PROJECTION_MAP;
    // URL_MATCHER と NOTES_LIST_PROJECTION_MAP を作成
    static {
    	// URI パース用
        URL_MATCHER = new ContentURIParser(ContentURIParser.NO_MATCH);
        URL_MATCHER.addURI("com.google.android.demo.provider.NotePad", "notes", NOTES);
        URL_MATCHER.addURI("com.google.android.demo.provider.NotePad", "notes/#", NOTE_ID);
		
        NOTES_LIST_PROJECTION_MAP = new HashMap<String, String>();
        NOTES_LIST_PROJECTION_MAP.put(BaseColumns._ID, "_id");
        NOTES_LIST_PROJECTION_MAP.put(NotePad.Notes.TITLE, "title");
        NOTES_LIST_PROJECTION_MAP.put(NotePad.Notes.BODY, "body");

    }
	/**
	 * 初期化処理
	 */
    @Override
	public boolean onCreate() {
        DatabaseHelper dbHelper = new DatabaseHelper();
       	mDB = dbHelper.openDatabase(getContext(), DATABASE_NAME, null, DATABASE_VERSION);
        return (mDB == null) ? false : true;    	
	}
	/**
	 * 削除処理
	 */
	@Override
	public int delete(ContentURI uri, String selection, String[] selectionArgs) {
		int count;
        switch (URL_MATCHER.match(uri)) {
        case NOTES:
            count = mDB.delete(DATABASE_TABLE, selection, selectionArgs);
            break;

        case NOTE_ID:
            String segment = uri.getPathSegment(1);
            count = mDB
                    .delete(DATABASE_TABLE, "_id="
                            + segment
                            + (!TextUtils.isEmpty(selection) ? " AND (" + selection
                                    + ')' : ""), selectionArgs);
            break;

        default:
            throw new IllegalArgumentException("Unknown URL " + uri);
        }

        getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}

	/**
	 * この URIが返すデータ型
	 */
	@Override
	public String getType(ContentURI uri) {
        switch (URL_MATCHER.match(uri)) {
        case NOTES:
            return "vnd.android.cursor.dir/vnd.google.note";

        case NOTE_ID:
            return "vnd.android.cursor.item/vnd.google.note";

        default:
            throw new IllegalArgumentException("Unknown URL " + uri);
        }
	}
	@Override
	/**
	 * データの挿入
	 */
	public ContentURI insert(ContentURI uri, ContentValues initialValues) {
	    long rowID;
	    ContentValues values;
		// データがなければ新規に作る
	    if (initialValues != null) {
	        values = new ContentValues(initialValues);
	    } else {
	        values = new ContentValues();
	    }
		// 正しいURI かどうかチェック
	    if (URL_MATCHER.match(uri) != NOTES) {
	    	throw new IllegalArgumentException("Unknown URI " + uri);
	    }

	    Resources r = Resources.getSystem();
		// タイトル設定
	    if (values.containsKey(NotePad.Notes.TITLE) == false) {
	        values.put(NotePad.Notes.TITLE, r.getString(android.R.string.untitled));
	    }
		// 本文設定
	    if (values.containsKey(NotePad.Notes.BODY) == false) {
	        values.put(NotePad.Notes.BODY, "");
	    }
		// 挿入
	    rowID = mDB.insert(DATABASE_TABLE, "note", values);
	    if (rowID > 0) {
            ContentURI newuri = NotePad.Notes.CONTENT_URI.addId(rowID);
            // 変更を通知
            getContext().getContentResolver().notifyChange(uri, null);
            return newuri;
        }

        throw new SQLException("Failed to insert row into " + uri);
    }
	/**
	 * データの取得
	 */
	@Override
	public Cursor query(ContentURI uri, String[] projection, String selection,
			String[] selectionArgs, String groupBy, String having,
			String sortOrder) {

		QueryBuilder qb = new QueryBuilder();

	    switch (URL_MATCHER.match(uri)) {
	    case NOTES:
	        qb.setTables(DATABASE_TABLE);
	        qb.setProjectionMap(NOTES_LIST_PROJECTION_MAP);
	        break;
        case NOTE_ID:
            qb.setTables(DATABASE_TABLE);
            qb.appendWhere("_id=" + uri.getPathSegment(1));
            break;
        default:
            throw new IllegalArgumentException("Unknown URL " + uri);
        }
        // ソート順がなければデフォルトのものを使う
        String orderBy;
        if (TextUtils.isEmpty(sortOrder)) {
            orderBy = NotePad.Notes.DEFAULT_SORT_ORDER;
        } else {
            orderBy = sortOrder;
        }
        // カーソル作成
        Cursor c = qb.query(mDB, projection, selection, selectionArgs, groupBy,
            having, orderBy);
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
	}
	/**
	 * 更新処理
	 */
	@Override
	public int update(ContentURI uri, ContentValues values, String selection,
			String[] selectionArgs) {
		int count;
		switch (URL_MATCHER.match(uri)) {
		// 全て
		case NOTES:
			count = mDB.update(DATABASE_TABLE, values, selection, selectionArgs);
	        break;
		// ID 指定
	    case NOTE_ID:
	        String segment = uri.getPathSegment(1);
	        count = mDB.update(DATABASE_TABLE, values, "_id="
	                            + segment
	                            + (!TextUtils.isEmpty(selection) ? " AND (" + selection
	                                    + ')' : ""), selectionArgs);
	        break;
		default:
	        throw new IllegalArgumentException("Unknown URL " + uri);
	    }
		// 更新を通知
		getContext().getContentResolver().notifyChange(uri, null);
	    return count;
	}

}

Naotepadv4 クラスの修正

DBHelper ではなく、ContentProvider クラスを利用するように変更します。

データを取得するのに、前回の例と同じように managedQuery メソッド が使えるようになっていると思います。

getIntent().setData(NotePad.Notes.CONTENT_URI);
cur = managedQuery(getIntent().getData(), PROJECTION, null, null);

の部分ですね。
また、編集時に編集画面へデータを渡すには、

ContentURI uri = getIntent().getData().addId({編集したい行の_ID});
startSubActivity(new Intent(Intent.EDIT_ACTION, uri), ACTIVITY_EDIT);

のように、編集したい行の URI を示す ContentURI クラスを渡します。

今回から、startSubActivity へ渡す Intent には、Intent.INSERT_ACTION や Intent.EDIT_ACTION という定数を渡しています。
このように、Intent で ACTION を指定する場合には、AndroidManifest.xml への記述が必要となります。
AndroidManifest.xml については、後述します。

package com.google.android.demo.notepad4;

import java.util.ArrayList;

import android.app.ListActivity;
import android.content.Intent;
import android.database.Cursor;
import android.net.ContentURI;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.view.Menu;
import android.view.View;
import android.view.Menu.Item;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.google.android.demo.provider.NotePad;

public class Notepadv4 extends ListActivity
{
    private static final int ACTIVITY_CREATE=0;
    private static final int ACTIVITY_EDIT=1;
    
    private static final int INSERT_ID = Menu.FIRST;
    private static final int DELETE_ID = Menu.FIRST + 1;

    private Cursor cur;
    private ArrayList<Long> mItems ;
    /**
     * DB から取得したい 項目
     */
    private static final String[] PROJECTION = new String[] {
            BaseColumns._ID, NotePad.Notes.TITLE};
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle)
    {
	    super.onCreate(icicle);
        setContentView(R.layout.notes_list);
        fillData();
    }
	// データ表示    
    private void fillData() {
        // クエリ発行
        getIntent().setData(NotePad.Notes.CONTENT_URI);
        cur = managedQuery(getIntent().getData(), PROJECTION, null, null);
		// ID保持用
    	mItems = new ArrayList<Long>();
    	// タイトル保持用
    	ArrayList<String> items = new ArrayList<String>(); 
		int titleIndex = cur.getColumnIndex(NotePad.Notes.TITLE);
		// データ取得
		while (cur.next()){
        	mItems.add(cur.getLong(cur.getColumnIndex(NotePad.Notes._ID)));
        	items.add(cur.getString(titleIndex));
		}
                
        // リスト作成
        ArrayAdapter<String> notes = 
            new ArrayAdapter<String>(this, R.layout.notes_row, items);
        setListAdapter(notes);
    }
    /**
     * オプションメニュー生成
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        menu.add(0, INSERT_ID, R.string.menu_insert);
        menu.add(0, DELETE_ID, R.string.menu_delete);
        return true;
    }
	/**
	 * メニュー選択時
	 */
    @Override
    public boolean onMenuItemSelected(int featureId, Item item) {
        super.onMenuItemSelected(featureId, item);
        switch(item.getId()) {
        case INSERT_ID:
            Intent i = new Intent(Intent.INSERT_ACTION, getIntent().getData());
            startSubActivity(i, ACTIVITY_CREATE);
            break;
        case DELETE_ID:
        	// データ削除
    		cur.moveTo(getSelection());
    		cur.deleteRow();
            fillData();
            break;
        }
        return true;
    }

	/**
	 * リストアイテムクリック
	 */
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);
        
		ContentURI uri = getIntent().getData().addId(mItems.get((int)getSelectionRowID()));
        
        startSubActivity(new Intent(Intent.EDIT_ACTION, uri), ACTIVITY_EDIT);
    }
	/**
	 * SubActivity 終了時
	 */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras) {
        super.onActivityResult(requestCode, resultCode, data, extras);
        
		fillData();
    }
}

NoteEdit.java の変更

NoteEdit.java 側も変更を加えます。
package com.google.android.demo.notepad4;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.ContentURI;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.google.android.demo.provider.NotePad;

public class NoteEdit extends Activity {
    private EditText titleText;
    private EditText bodyText;
    private Cursor cur ;
    
    private ContentURI mURI; 
    
    private static final String[] PROJECTION = new String[] {
        BaseColumns._ID, NotePad.Notes.TITLE, NotePad.Notes.BODY};

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.note_edit);
        
        titleText = (EditText) findViewById(R.id.title);
        bodyText = (EditText) findViewById(R.id.body);
        
        Button confirmButton = (Button) findViewById(R.id.confirm);

        final Intent intent = getIntent();

        final String action = intent.getAction();
        if (action.equals(Intent.INSERT_ACTION)) {
            // ContentResolver にURI を渡し、新規のカーソルを作成する
            mURI = getContentResolver().insert(intent.getData(), null);

            // エラー
            if (mURI == null) {
                Log.e("Notes", "Failed to insert new note into "
                        + getIntent().getData());
                finish();
                return;
            }

            // キャンセルボタンなどを実装したい場合、setResult には RESULT_CANCEL を渡すこと
            setResult(RESULT_OK, mURI.toString());
        }else if (action.equals(Intent.EDIT_ACTION)) {
            // 編集時
            mURI = intent.getData();
            setResult(RESULT_OK, mURI.toString());        	
        }
        // クエリを発行
        cur = managedQuery(mURI, PROJECTION, null, null);
        // データ表示
        populateField();
        // Submit ボタンクリック時のイベント
        confirmButton.setOnClickListener(new View.OnClickListener() {

            public void onClick(View arg0) {
                setResult(RESULT_OK);
                finish();
            }
            
        });
    }
    /**
     * アクティビティの終了、一時停止時
     */
    @Override
    protected void onPause() {
    	// 使っているリソースを開放する
    	super.onPause();
    	saveState();
    }
    /**
     * Pause 状態からの復旧時
     */
    @Override
    protected void onResume() {
    	// データを復旧
    	super.onResume();
    	populateField();
    }
    /**
     * データ保存用のメソッド
     */
    private void saveState(){
    	String title = titleText.getText().toString();
    	String body = bodyText.getText().toString();
        // cur.updateString で、データを更新		
        if (cur != null) {
            cur.updateString(cur.getColumnIndex(NotePad.Notes.TITLE), title);
            cur.updateString(cur.getColumnIndex(NotePad.Notes.BODY), body);
            managedCommitUpdates(cur);
        }
    }
    /**
     * 編集用のフィールド表示
     */
	private void populateField() {
        if (cur != null) {
            cur.first();
            String title = cur.getString(cur.getColumnIndex(NotePad.Notes.TITLE));
            titleText.setText(title);
            String body = cur.getString(cur.getColumnIndex(NotePad.Notes.BODY));
            bodyText.setText(body);
        }
    }
}

AndroidManifest.xml の更新

最後に、IntentFilter や provider の設定を、AndroidManifest.xml に記述します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.android.demo.notepad4">

    <application android:icon="@drawable/icon"
    			 android:theme="@android:style/Theme.Dark">
	    <provider class="NotesProvider" android:authorities="com.google.android.demo.provider.NotePad" />
        <activity class=".Notepadv4" android:label="@string/app_name">
            <intent-filter>
                <action android:value="android.intent.action.MAIN" />
                <action android:value="android.intent.action.INSERT" />
                <action android:value="android.intent.action.EDIT" />
                <category android:value="android.intent.category.LAUNCHER" />
                <type android:value="vnd.android.cursor.dir/vnd.google.note" />
            </intent-filter>
        </activity>
        <activity class=".NoteEdit" android:label="@string/edit_note">
            <intent-filter>
                <category android:value="android.intent.category.DEFAULT" />
                <type android:value="vnd.android.cursor.dir/vnd.google.note" />
                <type android:value="vnd.android.cursor.item/vnd.google.note" />
            </intent-filter>
        
        </activity>
    </application>
</manifest> 

<provider class="NotesProvider" android:authorities="com.google.android.demo.provider.NotePad" />
の部分が、今回作った ContentProvider を使う為の記述です。authorities 属性の value 値が、ContentURI に対応しているのがわかると思います。
<intent-filter>内の
<action android:value="android.intent.action.INSERT" />
<action android:value="android.intent.action.EDIT" />
は、Intent.EDIT_ACTION などを 別の Activity に渡す際に必要な記述になります。
<type android:value="vnd.android.cursor.dir/vnd.google.note" />
部分は、Provider の getType() に対応しています。

以上の変更を加えることで、オリジナルの ContentProvider を利用できるようになっています。
RUN してみると、これまでと同じように NotePad アプリケーションが動作すると思います。

今回はソースをまるまる貼り付けているので、かなりエントリが長くなってしまいました。

[Android] ContentProvider を使って コンタクトリストのデータを取得する

Android の ContentProvider クラス について。

チュートリアルにある NotePad アプリケーションではデータの保存に SQLite を使っていますが、

For this exercise, we are just going to use a SQLite database directly to store our data, but in a real application it would be much better to write a proper ContentProvider to encapsulate this behavior instead.

If you are interested, you can find out more about content providers or the whole subject of Storing, Retrieving, and Exposing Data.


とあるように、実際アプリケーションを作る場合は ContentProvider を使う方がいいようです。

というわけで、以下のURL を参考に ContentProvider を使ってみました。
http://code.google.com/android/devel/data/contentproviders.html

まずは、サンプルコードにあるように、アドレス帳のデータを取り出してリスト形式で表示してみることにします。

1. 新しい Android プロジェクトを作る。

プロジェクト名、アプリケーション名は ContactView にしました。

2. layout 用の xml ファイルを作成。

main.xml に ListView を追加
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"

    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <ListView id="@+id/android:list"
          android:layout_width="wrap_content"
        	android:layout_height="wrap_content"/>

  	<TextView id="@+id/android:empty"
          android:layout_width="wrap_content"
        	android:layout_height="wrap_content"
        	android:text="No Contacts!"/>
</LinearLayout>

リスト内の表示用に、contacts_row.xml を追加
<?xml version="1.0" encoding="utf-8"?>
<TextView id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

3. コンタクトリストからデータを表示するプログラムを書きます。

package jp.plantsweb.android.contact;

import java.util.ArrayList;

import java.util.List;

import android.app.ListActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class ContactList extends ListActivity {
    /** Called when the activity is first created. */

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);
        fillData();
    }

    /**
     * コンタクトデータを取得して表示
     */
    private void fillData(){
        List items = new ArrayList();

	     // コンタクトリストから取得するカラムを指定
	     String[] projection = new String[] {
   		    android.provider.BaseColumns._ID,
       		android.provider.Contacts.PeopleColumns.NAME
    	};
	     

	    // クエリを発行する
    	Cursor cur = managedQuery( android.provider.Contacts.Phones.CONTENT_URI,
                 projection, // 取得するカラムを指定
               	 null,             // WHERE句にあたる部分。全部必要なのでnull
                 android.provider.Contacts.PeopleColumns.NAME + " ASC"); // Order by

       	String name;

       	// データ取得用のインデックスを取得する
      	int nameColumn = cur.getColumnIndex(android.provider.Contacts.PeopleColumns.NAME); 
       	// データ取得開始
		while (cur.next()) {
  	        	// データ取得
      	    	name = cur.getString(nameColumn);

	      	    // 表示用の配列に追加
				items.add(name);
		}
     	// アダプタを介してデータ表示
       	ArrayAdapter contacts = new ArrayAdapter(

       		this, R.layout.contacts_row, items);
       	setListAdapter(contacts);
    }
}

Run してみたところ、SecurityException が発生しました。

error

デフォルトではコンタクトリストには許可されたアプリケーションしかアクセスできない模様です。

4. パーミッションの設定をする

セキュリティの章に答えがありました。 http://code.google.com/android/devel/security.html

保護されているデータにアクセスする場合、AndroidManifest.xml に、
<uses-permission id="android.permission.RECEIVE_SMS" />
というような宣言を書く必要があるようです。

パーミッションの一覧が以下の URL にあり、
http://code.google.com/android/reference/android/Manifest.permission.html
READ_CONTACT というパーミッションがありました。AndoroidManifest.xml に

<uses-permission id="android.permission.READ_CONTACTS" /> 

という行を追加して RUN してみたところ、無事コンタクトリストの内容が表示されました!


list of address

しかし、コンタクトリストには2名しか登録していないはずですが、4行になっていますね。調べてみたところ、ContactList 内のデータは、携帯と自宅と登録されている場合は2行分に分かれているようです。

次回はオリジナルの ContentProvider を作ってみようと思います。

携帯電話向けポッドキャスティングサービス

 モバイルファクトリー、携帯電話向けポッドキャスティングサービスを開始 - CNET Japan
http://japan.cnet.com/news/media/story/0,2000047715,20096594,00.htm

  CaspeeeはiTunesなどポッドキャストに対応した専用ソフトにファイルをダウンロードして視聴できるポッドキャストを提供している。 iPod などのmp3プレイヤーがない場合も、携帯電話でポッドキャスティングを利用できる。ただし、現時点ではドコモ、 auの一部端末のみに対応している。
携帯で番組が聞けるのはいいんですが、番組の作成は既存のソフトウェアなどで作る必要があるようです。
Webアプリケーションなどでもっと手軽に番組を配信できるようになるといいんですがね。

携帯電話の機種情報を提供するサービス

 VentureNow: セラン、携帯電話の機種情報配信サービスサイト「MOBYRENT」開設
http://www.venturenow.jp/news/2006/02/14/2000_011058.html

 「MOBYRENT」は、インターネット接続機能を持つ過去の機種から最新の機種までの情報を提供するASPサービス。現在、 約400種類以上ある携帯電話の機種情報を網羅し、検索、閲覧、最新情報のほか、 大手携帯ポータルサイトにアクセスしている携帯電話のアクセスシェア情報も提供する。機種の属性情報は、通信キャリア、シリーズ、 通信方式、機種名、メーカー、発売日、User-Agent、画面情報など約40項目の機種情報を閲覧できる

マルチキャリア対応のプラットフォームやCMS、 実機テストサービスや実機貸し出しサービスなんかは良くありますが、端末情報のみをASPで提供するというのは始めて聞きました。

モバイルサイトを開発しようとした場合の大きな障壁は、3つあります。

  1. デバイス情報を集めるのが大変
  2. 端末によってさまざまな癖がある
  3. 実機テストが困難

特に、着メロや着うたなどのマルチメディアコンテンツを配信する場合には、キャリア・ 端末によってさまざまなファイルフォーマットがあり、いわゆる「機種マスタ」というのは開発ベンダーにとって大きな財産です。

上にあるような状況が参入障壁となり携帯サイトが開発できるベンダーというのは非常に少なかったのですが、 これで新規ベンダーも参入しやすくなりそうですね。

セールスフォースがAPIを公開

営業向けのASPサービスである、SalesforceのAPIが一般公開されることになりました。

元ネタ:セールスフォース、「AppExchange」サービスを正式開始 - CNET Japan
http://japan.cnet.com/news/ent/story/0,2000047623,20094666,00.htm?ref=rss

  同社は米国時間1月17日に、マスコミと顧客企業を対象にしたイベントを開き、昨年9月に明らかにしていた 「AppExchange」を正式に立ち上げた。今回一般利用が可能になったこのソフトウェア・マーケットプレースは、 Salesforceの顧客やパートナー各社が、同社のASPプラットフォームを介してアプリケーションを配布・ 共有できるようにするものだ。

これまで、特定のベンダー向けにのみ公開されていたSalesforceのAPIが一般公開されています。
Salesforce上の顧客情報や売上データをGoogle Map上に表示させるデモなどが行われたようです。

実は、僕の使っているマインドマップツールである、MindManagerにもSalesforceとの連携プラグインがあったりします。
このようなことが、より多くのベンダーにてできるようになるということですね。
作った機能はSalesforceのサイト上にも登録でき、有料で販売することもできるようです。

AppExchangeからプログラムをダウンロードした顧客の数はすでに1800社を超えており、 さらに8万社が現在このテストを進めているとBenioff は語った。

社員一人一人にブログを持たせ、ブログの使われ方とSalesforceを連携させたり、 携帯電話と連携したサービス(KDDIがすでに作っていたりもしますが)を作ったり、いろいろな可能性がありそうです。

18,700社/351,000 ユーザが使っていることを考えると、ツールを作って有償で提供できたらそれなりのボリュームになりそうですね。

jig.jpの携帯電話向けストリーミング配信ツール

jig.jp、携帯電話向けストリーミング配信ツールを販売開始 - CNET Japan
http://japan.cnet.com/news/media/story/0,2000047715,20094533,00.htm?ref=rss

 jig.jpは1月25日、携帯電話向けに映像をストリーミング配信するためのツールを販売開始する。 コンテンツホルダーはファイルの長さやサイズの制限なしに映像を配信することが可能となる。

これまで、携帯電話向けにストリーミング動画を配信するのは結構大変でコストもかかりましたが、 jig.jpが19万8000円で変換ツールの販売を始めました。
jig.jpが提供するアプリでないと再生はできませんが、 携帯向けに動画配信関連のサービスを提供したい場合のハードルがぐっと下がりましたね。

jig.jpでは、映像プロデュース会社のアットムービー・ジャパンおよび携帯コンテンツの企画・開発・ 運営を手がけるシンクウェアの協力を受け、2月 18日より公開される予定の映画「シムソンズ」 の試写会を携帯電話上で2月5日に開催する。同社によれば携帯電話上での試写会は日本初の試みといい、 未公開の映画を携帯電話に配信する取り組みは世界初という。

シムソンズといえば、動画ブログにつっこみを入れれるで紹介したところですね。
映画の宣伝用サイトですが、いろいろ面白い試みをしていますね。

MotorolaがiRadioの詳細発表

ITmediaニュース:MotorolaがiRadioの詳細発表――次期ROKRにはiTunes非搭載
http://www.itmedia.co.jp/news/articles/0601/04/news001.html

 世界第2位の携帯電話メーカーであるMotorolaは1月3日、 有料音楽サービスで今年中にサービス開始予定のiRadioに関する計画の詳細を発表した。 同社は携帯電話とインターネット電話サービスをリンクできるコンシューマー向け家庭用電話も発表した。

この前のROKRはiTunesが搭載され話題を呼びましたが、 Appleとは決別して独自の音楽ネットワークを築くようです。

 iRadioは、ユーザーがコンピュータにチャンネルをダウンロードし、それを携帯に転送して再生したり、 衛星ラジオのようにカーステレオや家庭用ステレオで再生したりすることができる。

月額約7ドルだそうです。そういやWiFi-Podなどの言葉もありましたが、 やはり音楽を聴くためのデバイスのネクストステージはWi-Fi対応になっていくんでしょうね。 iPodでは自分が入れた曲しか聴けないですが、iRadioでは自分の知らない楽曲も聴くことができます。 iRadioには多くのラジオ局があり、

 Motorolaはサービスプロバイダーのパートナー名を明かさなかったが、 携帯電話の利用者がiRadioで気に入った楽曲を携帯サービスを通じてダウンロード購入できるような、 携帯キャリアと連携したサービス販売を期待している。

というようにキャリアのインフラを通じて手軽に決済ができるようになりそうです。 今までの携帯で音楽を聴くスタイルの中では一番進んだサービスとなるんじゃないでしょうか。

しかし、日本メーカーも十分魅力的なデバイスが作れそうなのに、なかなかそんな気配はありません。 moocsとかやっている場合じゃないです。 すでに大きく市場成長してしまった着うたや着うたフルとの整合性が難しい上にキャリアの力が強く、 SDカードを広めたいメーカーの思惑などもあったりするからなのでしょうか。

 

PASMOでお財布ケータイが一気に普及する?

元ネタ:パスネットの非接触IC版「PASMO」、2007年3月スタート-ケータイWatch
http://k-tai.impress.co.jp/cda/article/news_toppage/27110.html

 パスネット・バス連絡協議会は、関東の私鉄・地下鉄で利用できる磁気型乗車券「パスネット」の非接触ICカード版「PASMO」 が2007年3月より開始されると発表した。あわせてJR東日本のSuica、 およびモバイルSuicaとの相互乗り入れも同時期にスタートする。

再来年の3月とまだまだ先ですが、望まれていたサービスがやっと実現しますね。
お財布ケータイ、一度使うと結構便利ですが、まだまだ爆発的な普及にはいたっていません。
こういうのは「慣れ」によるものが大きいのでこういった強力なサービスによって、お財布ケータイ経験者が一気に増え、 だんだんと駅以外でもFelicaを使われるようになり、一般的な決済手段としても普及するようになるんじゃないでしょうか。

mobidec雑感

mobidecの感想です。本当は長文を予定してましたが、時間もだいぶたってしまったのであげちゃいます。

 キーメッセージとして言いたいのは、キャリア依存のビジネスモデルが崩壊し、 来年からは多種多様なビジネスモデルが生まれてくるだろうということです。

 メディアサイドの動きとしては、ケータイはすでにマスメディアと同等の重要度を持ち、 口コミを誘発するメディアとしての利用が大きく花開いていくであろうという点が注目です。定額制の普及や新規参入によって、 ユーザは公式課金モデルから一般サイトへの移行を始めています。キャリアへの依存度が低くなっていく中で、 広告モデルのメディアが大きく成長するでしょう。すでに、ECとオークションは大きく市場成長していますが、検索市場など、 周辺メディアの拡大も予想されます。
とはいえ、公式モデルでも引き続き、電子書籍などの分野は成長を遂げると思います。
音楽関連では、ケータイキャスティングなどの、新たなDL技術も検討されています。

 また、放送とケータイの融合に関して言えば、たとえばインデックスなどは、 映像コンテンツをマルチメディアで扱うにあたってのコンテンツ制作からパッケージ販売、デジタル配信にいたる流れを、 上流から下流まですべて、ソフト面、インフラ面共に解決する、ワンストップソリューションを打ち出しております。 この分野はTV局やインデックス、楽天などの大きなプレイヤー主導の世界であり、 よほどの技術力やコンテンツ力がないと入り込むのが難しいと感じました。

 BtoBの観点から考えた場合、MVNOの本格化により、「通信機能付の○○」というような多様な端末が出現することになります。 デバイスを巻きこんだ形での業務ソリューションというのが展開されるだろうと思いました。

 つらつらと書いてしまいましたが、「変化のあるところにチャンスあり」ってことで、いろいろ前向きにとらえて行こうと思っています。

 今回のmobidecでは、他のメディアとケータイの違いについていろいろと考えました。「モバイル2.0」 という言葉が今後流行っていくだろうとも思っています。
モバイル2.0に関してはまたそのうち書きたいと思います。「ケータイ」は日本発の文化であり、 前例がない市場だけになかなかエキサイティングです。

Home > ケータイ

Search
Feeds

Page Top