6月 05

そう言えば Froyo まだ来ません(泣)。かといっていまさら手動でアップデートするのも面倒なので気長に待つ事にしました。とはいっても Froyo でアプリが動かねーというメールもいただいていたので、久しぶりに「エミュレータ」を起動することにしました。(単にエミュレータの存在を今まで忘れていただけですけどねーw)

エミュレータで動作確認

早速、問題を指摘された「コールW Pro」の動作確認です。まずは連絡先を同期と思ったのですが、さあ困りました。エミュレータって Google アカウントと同期できないんでしたっけ?Exchange サーバとの同期を設定する画面が現れました。

しょうがないのでデータはローカルで作成です。今回は登録は1つだけでテスト可能なのでまだ良いのですが、多くのデータ登録が必要なテストが必要な場合は大変です。何とかならないものでしょうか?

不具合確認

準備ができたところで コールW Pro を起動させました。ついでに全てのコールWシリーズとメールWシリーズも表示させました。左が不具合有り、右が正常な表示です。

そうです。電話とメール表す画像の部分が正しく表示されていないのです。ただ、ここでおかしいと気付いたのは同じリソースを使っているはずの コールW Free の電話アイコンの画像は正しく表示できているのです。何でなんでしょう?実は コールW Free と他のアプリ(Pro と Quad)は、画像の表示させ方が違っているのです。コールW Free は画像の指定をレイアウトファイルの中で、その他(Pro/Quad)はコードの中でリソースを指定して表示させていました。

正しく表示されなかった方(コールW Pro/Quad)

    bitmap = BitmapFactory.decodeResource(
             context.getResources(), R.drawable.ic_dialog_call);   <---- ここ
    views.setImageViewBitmap(R.id.c_icon0, bitmap);

正しく表示された方(コールW Free)

    <ImageView
        android:id="@+id/c_icon0"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:layout_alignParentRight="true"
        android:layout_margin="2dp"
        android:src="@drawable/ic_dialog_call"     <---- ここ
        />

ちなみにこの問題が起きるのは画像に透過イメージを使った場合だけのようです。透過でないイメージに差し替えたところ、ここで挙げたコードでも画像が正しく表示できました。

これは仕様の変更なのでしょうか?それとも単なる Froyo の不具合なのでしょうか?何にせよ取りあえず解決です。めでたしめでたし


Posted by sak

Tagged with:
5月 01

日本でもようやく Android 2.x 端末(Desire)がソフトバンクから登場してきました。開発者にとってはソフトウェアの複数バージョン対応が現実的な課題になってきています。ただ、問題が発生する場合の多くは新機能に関するものが多く、1.x の機能の範囲内で使用していればそのまま動くことが多いのも事実です。

実は

前回の記事で紹介したソースコードにはさりげなく 2.x 対応が入っています。コールW/メールW では電話番号またはメールアドレスの選択を標準機能のコンタクトの情報に依存しているわけですが、このコンタクト情報の取り扱いが v1.x と v2.x では大きく変わっています。(2.x からはマルチコンタクトにも対応しており旧来のAPI設計では限界を生じたため今のうちに作り直してきたのでしょう。)このためで 1.x で動いているアプリでも 2.x 環境を考慮していないアプリは動かなくなります。

コンタクトの情報は ContentProvider と呼ばれる仕組みを使って他のアプリケーションからでも自由にアクセスできます。例えば、コールWではコンタクトから以下の4つの情報を一覧として取得しています。

  • 名前
  • 電話番号
  • タイプ(ex. 自宅、携帯、仕事)
  • 写真(※IDを取得後、そのIDでイメージを取得)

幸いなことにコールW/メールWで使っている範囲ではバージョンによって使用する文字列を差し替えるだけでOKでした。なお、このとき指定する文字列は本来ならば予め定義した定数を利用すれば一番なのですがバージョンのコードになるのを嫌いあえて避けました。端末の種類は以下のコードで識別できます。

boolean isEclair() { return Integer.valueOf(Build.VERSION.SDK) > 4; }

今回、差し替えた文字列は以下の通りです。

項目 Android 1.x Android 2.x
電話番号URI content://contacts/phones content://com.android.contacts/data/phones
名前 name display_name
電話番号 number data1
タイプ type data2
写真 person photo_id
写真URI content://contacts/photos content://com.android.contacts/data
写真データ data data15

なお、ソースを添付するので参考にしてください。前回添付したソースはあまりいけていなかったので、若干見直しました。また、前回より公開する範囲を少し広くしました。

    static {
        if (isEclair()) {
            _PHONES_CONTENT_URI = Uri.parse("content://com.android.contacts/data/phones");
            _PHOTOS_CONTENT_URI = Uri.parse("content://com.android.contacts/data");
            _NAME   = "display_name";
            _NUMBER = "data1";
            _TYPE   = "data2";
            _PERSON = "photo_id";
            _PHOTO  = "data15";
           
        } else {
            _PHONES_CONTENT_URI = Uri.parse("content://contacts/phones");
            _PHOTOS_CONTENT_URI = Uri.parse("content://contacts/photos");
            _NAME   = "name";
            _NUMBER = "number";
            _TYPE   = "type";
            _PERSON = "person";
            _PHOTO  = "data";
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.contact_list);
        :
      (中略)
        :
        Cursor c = getContentResolver().query(_PHONES_CONTENT_URI, null, filter, null, sort);
        startManagingCursor(c);
       
        SimpleCursorAdapter adapter =
            new MySimpleCursorAdapter(
                this,
                R.layout.contact_item,
                c,
                new String[] {
                        _NAME,
                        _NUMBER,
                        _TYPE,
                        _PERSON,
                },
                new int[] {
                        R.id.name,
                        R.id.number,
                        R.id.type,
                        R.id.person,
                }
            );
       
        ListView listView = (ListView) findViewById(android.R.id.list);
        listView.setAdapter(adapter);
        :
      (中略)
        :
    }

    public class MySimpleCursorAdapter extends SimpleCursorAdapter {
       
        public MySimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to)
        {
            super(context, layout, c, from, to);
            setViewBinder(new MyViewBinder());
        }
    }

    public class MyViewBinder implements SimpleCursorAdapter.ViewBinder {
       
        @Override
        public boolean setViewValue(View view, Cursor cursor, int columnIndex)
        {
            if (columnIndex == cursor.getColumnIndex(_PERSON)) {
                String _id = cursor.getString(columnIndex);
                if (_id == null) {
                    ((ImageView)view).setImageResource(R.drawable.ic_contact_picture);
                    return true;
                }
                Uri uri = ContentUris.withAppendedId(_PHOTOS_CONTENT_URI, Integer.parseInt(_id));
                String[] projection = { _PHOTO };
               
                Cursor c = managedQuery(uri, projection, null, null, null);
                c.moveToFirst();
                byte[] photoData = c.getBlob(0);
                c.close();
               
                if (photoData != null)
                    ((ImageView)view).setImageBitmap(BitmapFactory.decodeByteArray(photoData, 0, photoData.length));
                else
                    ((ImageView)view).setImageResource(R.drawable.ic_contact_picture);
                return true;
       
            } else if (columnIndex == cursor.getColumnIndex(_TYPE)) {
                String type;
                int ntype = Integer.parseInt(cursor.getString(columnIndex));
                if (ntype == _TYPE_MOBILE)
                    type = getString(R.string.type_mobile);
                else if (ntype == _TYPE_HOME)
                    type = getString(R.string.type_home);
                else if (ntype == _TYPE_WORK)
                    type = getString(R.string.type_work);
                else
                    type = getString(R.string.type_other);
                ((TextView)view).setText(type);
                return true;
            }
            return false;
        }
    }


Posted by sak

Tagged with:
preload preload preload