2013年3月18日月曜日

ショートカット情報を取得してデータベースへ登録する

ホームアプリでは当たり前に存在するショートカット機能だが、
自分のアプリ内にショートカット機能を実装するに当たって調べた
結果をメモしておく。ここではショートカットを永続的に使用するため、
データベースに保存する事を前提とする。

【基本的な流れ】
(1)ショートカット一覧画面を起動する。
(2)ショートカット一覧から選んで、他アプリのショートカット作成画面を起動するためのインテントを取得する。
(3)他アプリのショートカット作成画面を表示した後、必要なショートカット情報を受け取る。
(4)受け取ったショートカット情報をデータベースに格納する。
(5)データベースに格納したショートカット情報を取得する。

【詳細内容】
(1)ショートカット一覧画面を起動する。

ショートカット一覧を表示するのに一番簡単な方法は、下記のようにショートカット
一覧を取得するインテントを作成してstartActivityForResult()に渡す方法である。
Intent intent = new Intent(Intent.ACTION_PICK_ACTIVITY);
intent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT));
intent.putExtra(Intent.EXTRA_TITLE, "ショートカット一覧");
 
startActivityForResult(intent, REQUEST_PICK_SHORTCUT);

(2)ショートカット一覧から選んで、他アプリのショートカット作成画面を起動する。

onActivityResult()でショートカット作成画面のインテントを受け取って、
他アプリのショートカット作成画面を起動する。
private static final int REQUEST_PICK_SHORTCUT = 1;
private static final int REQUEST_CREATE_SHORTCUT = 2;

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if(requestCode == REQUEST_PICK_SHORTCUT){
        if(resultCode == RESULT_OK){
            startActivityForResult(data, REQUEST_CREATE_SHORTCUT);
        }
    }
}

(3)他アプリのショートカット作成画面を表示した後、必要なショートカット情報を受け取る。

onActivityResult()で受け取るショートカット情報は最大で以下の4つとなる。
・ショートカット名(String)
・インテント(Intent)
・アイコン(Bitmap)
・アイコンリソース(ShortcutIconResource)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if(requestCode == REQUEST_CREATE_SHORTCUT){
        if(resultCode == RESULT_OK){
            // ショートカット名
            String title = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
            // インテント
            Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
            // アイコン
            Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
            // アイコンリソース
            Parcelable iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
        }
    }
}

(4)受け取ったショートカット情報をデータベースに格納する。

4つの情報が他アプリで全て設定されているかどうかは分からないため、
データベースへ格納するためにnullチェック等を行って整形する必要がある。
整形した後にショートカットに必要なショートカット名、アイコン、インテントを
データベースに追加する。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if(requestCode == REQUEST_CREATE_SHORTCUT){
        if(resultCode == RESULT_OK){
            String title = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
            
            // あり得ないと思うが念のためnullチェック
            if(title == null){
                title = "";
            }

            // データベースにTEXT形式で保存するためURIの文字列にエンコード
            Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);            
            String uriString = intent.toUri(0);

            Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
            Bitmap icon = null;

            // 取得したアイコンがビットマップの場合はそのまま設定
            if(bitmap != null && bitmap instanceof Bitmap) {
                icon = drawableToBitmap(new BitmapDrawable((Bitmap)bitmap));
            } else{
                Parcelable iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);

                // アイコンが設定されていない場合はアイコンリソースからアイコンを取得を取得して適切なサイズに変換
                if(iconResource != null && iconResource instanceof ShortcutIconResource) {
                    try{
                        ShortcutIconResource shortcutIconResource = (ShortcutIconResource)iconResource;
                        final PackageManager packageManager = getActivity().getPackageManager();
                        Resources resources = packageManager.getResourcesForApplication(shortcutIconResource.packageName);

                        if(resources != null){
                            int id = resources.getIdentifier(shortcutIconResource.resourceName, null, null);
                            icon = drawableToBitmap(resources.getDrawable(id));
                        }
                    } catch(NameNotFoundException e){
                    }
                }
            }

            if(icon == null){
                // それでもアイコンデータが無い場合はシステムの適当なアイコンを使用
                // あるいは自前でデフォルトアイコンを用意する
            }

            // アイコンをByte[]に変換
            byte[] iconBytes = bitmapToByte(icon);

            if(iconBytes == null){
                // それでもnullになったらシステムの適当なアイコンを使用…
            }

            // データベースに追加
            ShortcutDatabaseHelper db = ShortcutDatabaseHelper.getInstance(getActivity());
            long databaseId = db.insert(title, uriString, iconBytes);
        }
    }
}

// DrawableをBitmapに変換
private void drawableToBitmap(Drawable drawable){
    Bitmap bitmap;

    if(drawable instanceof BitmapDrawable){
        bitmap = ((BitmapDrawable)drawable).getBitmap();
    } else{
        int width  = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();

        // 透過情報は保持したままARGB_4444を指定して画像品質を落とす
        Bitmap.Config bitmapConfig = drawable.getOpacity() != PixelFormat.OPAQUE ?
                Bitmap.Config.ARGB_4444 : Bitmap.Config.RGB_565;
        bitmap = Bitmap.createBitmap(width, height, bitmapConfig);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, width, height);
        drawable.draw(canvas);
    }

    return bitmap;
}

// Bitmapをbyte[]に変換
private void bitmapToByte(Bitmap icon){
    byte[] data = null;

    int size = icon.getWidth() * bitmap.getHeight() * 4;
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(size);

    try{
        icon.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
        outputStream.flush();
        outputStream.close();

        data = outputStream.toByteArray();
    } catch(IOException e) {
    } finally{
        if(outputStream != null){
            try {
                outputStream.close();
            } catch(IOException e) {
            }
        }
    }

    return data;
}

データベースのクラスShortcutDatabaseHelperは以下の通り。

public class ShortcutDatabaseHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "shortcut.db";
    private static final int DB_VERSION = 1;
    private static final String TABLE_NAME = "shortcut_table";
    private static final String COLUMN_ID = "_id";
    private static final String COLUMN_TITLE = "title";
    private static final String COLUMN_INTENT = "intent";
    private static final String COLUMN_ICON = "icon";

    // データベースのテーブルのカラムは(_id, title, intent, icon)の順としている
    private static final String SQL_CREATE_TABLE = 
            "CREATE TABLE " + TABLE_NAME + "( " +
            COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
            COLUMN_TITLE + " TEXT NOT NULL," +
            COLUMN_INTENT + " TEXT NOT NULL," +
            COLUMN_ICON + " BLOB NOT NULL," +
            ");";

    private static ShortcutDatabaseHelper databaseHelper = null;

    public ShortcutDatabaseHelper(Context context){
        super(context, DB_NAME, null, DB_VERSION);
    }

    public static ShortcutDatabaseHelper getInstance(Context context){
        if(databaseHelper == null){
            databaseHelper = new ShortcutDatabaseHelper(context);
        }

        return databaseHelper;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }

    // ショートカット情報をデータベースに追加するメソッド
    public long insert(String title, String uriString, byte[] iconBytes){
        ContentValues values = new ContentValues();
        values.put(COLUMN_TITLE, title);
        values.put(COLUMN_INTENT, uriString);
        values.put(COLUMN_ICON, iconBytes);

        SQLiteDatabase db = databaseHelper.getWritableDatabase();
        long index = db.insert(TABLE_NAME, null, values);
        db.close();

        return index;
    }

}

(5)データベースに格納したショートカット情報を取得する。

ShortcutDatabaseHelperの中で全ての情報を取得する処理を追加する。
データベースID(_id)は後に更新、削除をするために必要なので
取得しておく。
// 全てのショートカット情報を取得するためのメソッド
public ArrayList<shortcutdata> getAll(){
    SQLiteDatabase db = databaseHelper.getReadableDatabase();
    String querySql = "SELECT * FROM " + TABLE_NAME + ";";
    Cursor cursor = db.rawQuery(querySql, null);
    boolean isEof = cursor.moveToFirst();
    ArrayList<shortcutdata> shortcutList = new ArrayList<shortcutdata>();

    while(isEof){
        ShortcutData ShortcutData = new ShortcutData();

        int databaseId = cursor.getInt(0);
        ShortcutData.setDatabaseId(databaseId);

        String title = cursor.getString(1);
        ShortcutData.setTitle(title);

        String uriString = cursor.getString(2);
        Intent intent;
        
        try {
            // パースしてデコード
            intent = Intent.parseUri(uriString, 0);
        } catch(URISyntaxException e){
            // あり得ないがとりあえずインテントを生成してACTION_VIEWを設定(意味無し)
            intent = new Intent(Intent.ACTION_VIEW);
        }

        ShortcutData.setIntent(intent);

        // ビットマップオプションを指定してデコード
        byte[] icon = cursor.getBlob(3);
        BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap bitmap = BitmapFactory.decodeByteArray(icon, 0, icon.length, options);
        ShortcutData.setIcon(bitmap);

        shortcutList.add(ShortcutData);
        
        isEof = cursor.moveToNext();
    }

    cursor.close();
    db.close();

    return shortcutList;
}

// ショートカット情報格納クラス
public class ShortcutData{

    private int databaseId = 0;
    private String title = "";
    private Intent intent = null;
    private Bitmap bitmap = null;

    public void setDatabaseId(int database){ this.databaseId = databaseId; }
    public void setTitle(String title){ this.title = title; }
    public void setIntent(Intent intent){ this.intent = intent = intent; }
    public void setIcon(Bitmap icon){ this.icon = icon; }
    public int getDatabaseId(){ return databaseId; }
    public String getTitle(){ return title; }
    public Intent getIntent(){ return intent; }
    public Bitmap getIcon(){ return icon; }

}

以上でショートカット取得からデータベース保存、データベースから
取得までの流れを実装で追ってみた。今回参考になったのはAndroidの
標準ランチャー(Launcher2)のソースコードなので、自分のアプリに
ショートカット機能を組み込みたい人は一度目を通すといいと思う。

0 件のコメント:

コメントを投稿