ページ

2014/04/27

[Android]AppWidgetを作成する

以下の記事を読めば AppWidget を作成する方法がわかります。ココはポインタ。
App Widgets | Android Developers
Widgets | Android Developers
App Widget Design Guidelines | Android Developers
AppWidgetManager | Android Developers
AppWidgetProvider | Android Developers
AppWidgetProviderInfo | Android Developers
RemoteViews | Android Developers

AndroidManifect.xml に AppWidgetProvider を宣言する

<receiver android:name="ExampleAppWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/appwidgetinfo" />
</receiver>

AppWidget のメタデータを設定する

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/appwidget"
    android:minHeight="40dp"
    android:minWidth="40dp"
    android:updatePeriodMillis="0" >
</appwidget-provider>
最低限必要なのはレイアウト指定とサイズ指定と更新間隔指定です。
サイズ指定は以下を参考に指定します。
App Widget Design Guidelines | Android Developers
# of Cells
(Columns or Rows)
Available Size (dp)
(minWidth or minHeight)
140dp
2110dp
3180dp
4250dp
n70 × n − 30
更新間隔は最短で30分です。
30分 = 30 * 60 * 1000 = 1800000 以下の値を設定しても 1800000 として処理されます。
デバイスがスリープ状態でもアウェイクしてしまうため頻度の高い更新をする場合は
updatePeriodMillis に 0 を指定し、 ELAPSED_REALTIME か RTC のどちらかを指定した AlarmManager で
PendingIntent を飛ばして BroadcastReciever から AppWidget を更新する方法で行うべきです。

AppWidgetProvider を作成する

public class ExampleAppWidgetProvider extends AppWidgetProvider {

    /**
     * 最初の1個目のAppWidgetが設置された時に呼ばれる
     */
    @Override
    public void onEnabled(Context context){
        super.onEnabled(context);
    }

    /**
     * AppWidget が設置された時/更新期間が来た時に呼ばれる
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        for(int appWidgetId : appWidgetIds){
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    /**
     * AppWidget が削除された時に呼ばれる
     */
    @Override
    public void onDeleted(Context context, int[] appWidgetIds){
        super.onDeleted(context, appWidgetIds);
    }

    /**
     * 最後の AppWidget が削除された時に呼ばれる
     */
    @Override
    public void onDisabled(Context context){
        super.onDisabled(context);
    }

    /**
     * 上記のコールバックに振り分けるメソッド
     */
    @Override
    public void onReceive(Context context, Intent intent){
        super.onReceive(context, intent);
    }

    /**
     * AppWidget をリサイズした時に呼ばれる
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions){
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
        updateAppWidget(context, appWidgetManager, appWidgetId);
    }

}
ただの BroadcastReceiver の拡張なので時間のかかる処理を行うと ANR が発生するので
Service を開始して Service で時間のかかる処理を行った方が良いです。

View を設定する

public static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId){
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
    // AppWidgetManager 
    appWidgetManager.updateAppWidget(appWidgetId, views);
}
Creating the App Widget Layout | App Widgets | Android Developersを参考に
RemoteViews で使える View を使って AppWidget を作りましょう!

2014/04/25

[Android]画面サイズのダイアログを表示する

Days Counterで画面サイズのダイアログ Activity を表示したかったのですが
テーマなどでは上手いこと設定できなかったので調べてみたら
以下のようにプログラムから指定するようです。
WindowManager.LayoutParams params = getWindow().getAttributes();
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.MATCH_PARENT;
getWindow().setAttributes(params);

2014/04/24

[Mac][zsh]時間のかかるコマンドが終わったら自動で通知する

巨大なプロジェクトの clone とか時間のかかるコマンドを実行したりすると
待ってるのが暇なので他のことをやり始めたりするんですが、
気がついたらコマンドが実行完了してて時間を浪費してしまうので
コマンドが終わったら自動で通知してくれるようにしました。

参考: Macで時間のかかるコマンドが終わったら、自動で通知するzsh設定 - Qiita

Requirements

Mac OS X 10.8 以上
Terminal.app または iTerm2.app

Terminal-notifier.app をインストールする

alloy/terminal-notifier

Download

Prebuilt binaries are available from the releases section.

Or if you want to use this from Ruby, you can install it through RubyGems:
[sudo] gem install terminal-notifier

You can also install it via Homebrew:
brew install terminal-notifier
HomeBrew で インストールしようと思ったらフォルダ構成が変わったのか 404 になってしまったので
Releases · alloy/terminal-notifier から緑のボタンの zip をダウンロードしました。
解凍して /Applications にDrag&Drop でインストール完了です。

zsh-notify をインストールする

marzocchi/zsh-notify を zip でダウンロードしてきて解凍して ~/.zsh/ とかに入れます。
以下のように ~/.zshrc に記述して zsh-notify を有効にします。
# add-zsh-hook を有効にする | zsh-notify を有効にする
autoload -Uz add-zsh-hook
source ~/.zsh/zsh-notify/notify.plugin.zsh
参考: zsh-notify x Growl - 人生いきあたりばったりで生きてます@はてな

zsh-notify をカスタマイズする

SYS_NOTIFIER に Terminal-notifier のパスを指定したり、
NOTIFY_COMMAND_COMPLETE_TIMEOUT(※)で閾値を変更したり、
通知のタイトルを変えたりしてみました。
(※実行時間がこの秒数を超えたコマンドが実行完了した際に Terminal がバックグラウンドだったら通知される)
# vim: set nowrap filetype=zsh:
#
# See README.md.
#
fpath=($fpath `dirname $0`)

SYS_NOTIFIER="/Applications/terminal-notifier.app/Contents/MacOS/terminal-notifier"
NOTIFY_COMMAND_COMPLETE_TIMEOUT=3

# Default timeout is 30 seconds.
[[ $NOTIFY_COMMAND_COMPLETE_TIMEOUT == "" ]]  \
  && NOTIFY_COMMAND_COMPLETE_TIMEOUT=30

# Notify an error with no regard to the time elapsed (but always only
# when the terminal is in background).
function notify-error {
  local icon
  icon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns"
  notify-if-background -t "Failed!" --image "$icon" < /dev/stdin &!
}

# Notify of successful command termination, but only if it took at least
# 30 seconds (and if the terminal is in background).
function notify-success() {
  local now diff start_time last_command

  start_time=$1
  last_command="$2"
  now=`date "+%s"`

  ((diff = $now - $start_time ))
  if (( $diff > $NOTIFY_COMMAND_COMPLETE_TIMEOUT )); then
    notify-if-background -t "Success!" <<< "$last_command" &!
  fi
}

# Notify about the last command's success or failure.
function notify-command-complete() {
  last_status=$?
  if [[ $last_status -gt "0" ]]; then
    notify-error <<< $last_command
  elif [[ -n $start_time ]]; then
    notify-success "$start_time" "$last_command"
  fi
  unset last_command start_time last_status
}

function store-command-stats() {
  last_command=$1
  last_command_name=${1[(wr)^(*=*|sudo|ssh|-*)]}
  start_time=`date "+%s"`
}


autoload -U tell-terminal
autoload -U tell-iterm2
autoload -U notify-if-background
add-zsh-hook preexec store-command-stats
add-zsh-hook precmd notify-command-complete
こういうの読めばわかる気がしてくるので ShellScript 勉強しようって思います。
途中まで読んで積ん読になっている入門UNIXシェルプログラミングを読んで色々できるようになりたい。

変更を反映する

source ~/.zshrc
忘れずに。

通知をテストする

成功
sleep 31
実行時間が 30秒 (デフォルトの NOTIFY_COMMAND_COMPLETE_TIMEOUT の値)を超え、
バックグラウンドなら通知されます。
失敗
sleep 3 && ls nofile
実行時間が 30秒 (デフォルトの NOTIFY_COMMAND_COMPLETE_TIMEOUT の値)を超えなくても
失敗時は(バックグラウンドなら)通知されます。

大変便利です!

2014/04/23

[Android]FrameLayoutでMarginを指定して絶対配置を行う

一時的な View を任意の位置に配置したかったので
プログラムから配置しようと思ったら
いまいち上手く行かなかったのでメモしておきます。

どうやら MarginLayoutParams を使うのがミソだったみたい。
ViewGroup.MarginLayoutParams | Android Developers

というわけで任意の位置・サイズで FrameLayout に表示するコードです。
MarginLayoutParams params = (MarginLayoutParams)childView.getLayoutParams();
params.width = w;
params.height = h;
params.setMargins(x, y, 0, 0);
childView.setLayoutParams(params);
frameLayout.addView(childView);
参考: Local Wisdom Setting Margin in a FrameLayout Programmatically | Local Wisdom

2014/04/22

[Android]ImageViewのScaleTypeと縦横比を維持したままImageViewを拡大縮小する方法

ImageView の ScaleType や adujstViewBounds を設定した場合の挙動って
Layout Editor のビジュアル表示だとわかりにくいですよね。
実機との表示が違ったりして良い感じの表示にできなくて苦戦すること、あると思います。
面倒なので ImageView のパラメータをその場で変更できるアプリを作りました。
色々変更して自分が求めていた組み合わせを見つけて下さい。

wada811/ImageViewScaling

MATRIX: Matrix を使用して拡大縮小する。
FIT_XY: 縦横比を無視して View サイズに合わせて拡大縮小する。
FIT_CENTER: 縦横比を維持したまま View サイズに合わせて拡大縮小する。Gravity.CENTER
FIT_START: 縦横比を維持したまま View サイズに合わせて拡大縮小する。Gravity.TOP
FIT_END: 縦横比を維持したまま View サイズに合わせて拡大縮小する。Gravity.BOTTOM
CENTER: 拡大縮小しない。Gravity.CENTER
CENTER_INSIDE: View サイズに入るように縦横比を維持したまま等倍・縮小する。Gravity.CENTER
CENTER_CROP: View サイズを覆うように縦横比を維持したまま等倍・拡大する。Gravity.CENTER
ImageView.ScaleType | Android Developers
Matrix.ScaleToFit | Android Developers

上記の ScaleType を指定しても View サイズは変わりません。
画像を画面幅に合わせて縦横比を維持したまま拡大して表示したい場合、
以下のように設定することで(図左)のように表示することができます。(水色は ImageView の背景色)
layout_width: match_parent
layout_height: match_parent
ScaleType: FIT_START

しかし、ImageView のサイズが match_parent なので
ImageView を最大サイズで表示して下の余った領域で他の UI パーツを表示することが難しいです。
そこで以下のように記述することで(図右)のように ImageView 自体を拡大して表示することができます。
あとは RelativeLayout で ImageView の下の余った領域で他の UI パーツを表示することができます。
public void fitDisplayInside(Context context, ImageView imageView){
    Bitmap bitmap = BitmapUtils.createBitmapFromDrawable(imageView.getDrawable());
    float factor = (float)DisplayUtils.getWidth(context) / bitmap.getWidth();
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(DisplayUtils.getWidth(context), (int)(bitmap.getHeight() * factor));
    imageView.setLayoutParams(params);
    Matrix matrix = imageView.getImageMatrix();
    matrix.reset();
    matrix.postScale(factor, factor);
    imageView.setImageMatrix(matrix);
}

BitmapUtils#createBitmapFromDrawable wada811/AndroidLibrary-wada811
DisplayUtils#getWidth wada811/AndroidLibrary-wada811

参考: JavaScriptでギャルゲーを作る : 縦横比を維持したまま、ImageViewの拡大縮小を行う方法

追記(2014/04/27): 上の処理をしなくても(図右)のようにできました

layout_width: match_parent
layout_height: wrap_content
ScaleType: FIT_START
adjustViewBounds: true
領域の幅と高さのどちらに合わせるかでどちらが wrap_content か任意の大きさになるかは異なりますが、
上記のように指定すれば高さが画像の比率に合わせて変わるので(図右)のようになります。

2014/04/21

[Android]テキストサイズの指定方法

Android のテキストサイズの指定方法がわかりにくかったのでまとめました。
以下のように dimens.xml に 定義したテキストサイズを指定する際に
レイアウトで指定する方法はわかると思います。
コードから指定する方法も
TextView#setTextSize(float)は数値を渡すだけなのでわかると思います。
しかし、 getResources().getDimension(R.dimen.textSize) の値を渡すと
意図しないサイズになります。
これは Resources#getDimension()で解像度別のサイズに変換されるためです。
そのため TextView#setTextSize(int, float)でピクセル単位としてサイズを指定する必要があります。

まとめ

<resources>
    <dimen name="textSize">18sp</dimen>
</resources>

レイアウトで指定

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="@dimen/textSize" />

コードで指定(1)

textView.setTextSize(18);

コードで指定(2)

textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.textSize));

参考: dimens.xmlを使ってテキストサイズ指定 – Android – ごろつきめも

おまけ: ユーザー設定の文字サイズに影響されない文字サイズ指定方法

float density = getResources().getDisplayMetrics().density;
float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
float scale = scaledDensity / density;
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.textSize) / scale);

左から、[設定 > 画面設定 > フォントサイズ] を小・中・大・特大にした結果です。
demo の上2行はユーザー設定によって文字サイズが変わっていますが、
一番下の行はユーザー設定の文字サイズに影響されず固定サイズになっています。
文字サイズが変更できないのはユーザーの意思を無視することになるので微妙かもしれませんが、
タイトルやタブのテキストなど表示領域に制限があり、
文字サイズが大きくなるとレイアウトが崩れる場合はこのように指定するのもアリかもしれません。

参考: Y.A.M の 雑記帳: Android TextView の setTextSize() は sp 単位だよ!

ユーザー設定の文字サイズ変更でレイアウトが崩れないかがテスト項目に追加されました。← New!
こういった View がちゃんと表示されているかのテストってやっぱり人が確認するしかないんですかね…
OpenCV とかで画像処理で正常な画面と比較して〜みたいなのを見た覚えがあるけどそこまでしなくてはダメなのかなぁ。

追記: 2014/05/08

ユーザー設定の文字サイズの倍率を頑張って計算していましたが以下のコードで取得できることがわかりました。
float scale = getResources().getConfiguration().fontScale;

2014/04/06

かずー氏アイコンジェネレータをHTML5で作ってみた

HTML/CSS/JavaScriptを駆使した画像の重ね方 | DevAchieveという記事、
かずー氏クラスタアイコンの作り方の記事を1年前くらいに書いていました。
そこで Canvas を使用してかずー氏 (@kazoo04)のアイコン背景と
@wada811 のアイコンを合成できていたので
ユーザーがアップロードした画像を読み込んで
同じように Canvas に表示してあげて合成すれば
かずー氏アイコンジェネレータになるのではないか
というのがこのプロジェクトの始まりでした。

そして、途中で開発が止まるなど紆余曲折がありましたがついに完成しました!
かずー氏アイコンジェネレータ

わずか 3STEP で、かずー氏アイコンを作成できるという優れものです。
(本当はページ開いた瞬間にファイル選択が出て、選択したら自動ダウンロードということもできます。)
今回サーバいらずで、クライアントサイドの技術のみでかずー氏アイコンを生成することができます。
この手のちょっとしたネタサイト作るためにサーバの用意とかお金かけるとかしたくない人におすすめです。

技術的な話

Drag and Drop API

実は画像をドラッグ・アンド・ドロップでアップロードすることが可能です。
デザイン上、ドラッグ・アンド・ドロップができるとわかるようになっていないので
何かいい感じのデザインになる Pull Request を待っています。

Drag and Drop API 機能の実装は簡単で、ドラッグ・アンド・ドロップ系のイベントを適切に処理するだけです。
<canvas id="jsKazoo04Icon"></canvas>
$(document).ready(function(){
    // ...
    addDropListener('jsKazoo04Icon');
});

function addDropListener(id){
    var jsDropBox = document.getElementById(id);
    document.addEventListener('dragover', dropNone);
    document.addEventListener('drop', dropNone);
    jsDropBox.addEventListener('dragover', dropCopy);
    jsDropBox.addEventListener('drop', drop);
}
ブラウザに画像ファイルをドロップすると画像をそのまま表示してしまうので処理を止めています。
document.addEventListener('dropover', dropNone); 
document.addEventListener('drop', dropNone);
function dropNone(event) {
    event.stopPropagation(); // イベントのバブリング(親要素への伝搬)を停止する
    event.preventDefault(); // ブラウザのデフォルトの処理(画像の表示)を防ぐ
    event.dataTransfer.dropEffect = 'none'; // D&D中のファイルにアイコンがつかないようにする
}
ドロップを受け付ける要素に addEventListener でリスナーを登録します。
jsDropBox.addEventListener('dragover', dropCopy);
jsDropBox.addEventListener('drop', drop);
function dropCopy(event) {
    event.stopPropagation(); // イベントのバブリング(親要素への伝搬)を停止する
    event.preventDefault(); // ブラウザのデフォルトの処理(画像の表示)を防ぐ
    event.dataTransfer.dropEffect = 'copy'; // D&D中のファイルにプラスアイコンがつくようにする
}

function drop(event){
    // 画像ビューアとして開かないようにする
    dropCopy(event);
    // ドロップされたファイルを取得
    var file = event.dataTransfer.files[0];
    if(file.type !== 'image/png'){
        alert('PNG画像のみを受け付けています。');
        return;
    }
    handleFile(file);
}

参考
[HTML5] Drag & Drop API おさらい 「ファイルの DnD」 | Developers.IO

JavaScript から参照する要素には js プレフィックスをつけたほうがいいよという話。
CSSのセレクタ部分(IDやCLASS)にハイフンとか使われるの好きじゃないなー。ボクはあまり好きじゃないなー - latest log

File API

input[type="file"] で画像アップロードを受け付け、
アップロードされた画像をプレビュー表示する際に使用しました。
<input type="file" id="jsUpload" class="upload" onchange="upload(this.value)" accept="image/*" />

まず、画像のみを受け付けるためには accept="image/*" を指定します。
参考
HTML5時代のinput要素のaccept属性について | 富永日記帳
4.10.5.1 States of the type attribute — HTML Standard

input[type="file"] で画像アップロードした際のイベントを取得するには onchange="upload(this.value)" を指定します。
今回は fileName は未使用ですが、何かに使えるかも?
スマホの場合はセキュリティのためダミーのパスが入るので使えないかも?
function upload(fileName){
    var files = document.getElementById('jsUpload').files;
    var file = files[0];
    handleFile(file);
}
File API で画像を読み込みます。
function handleFile(file){
    var fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    // ファイル読み込み失敗時
    fileReader.onerror = function(e){
        alert('画像の読み込みに失敗しました。');
    };
    // ファイル読み込み完了時
    fileReader.onload = function(event){
        var image = new Image();
        image.src = event.target.result;
        image.onload = function(){
            // 画像が読み込み完了したら Canvas に描画
            var canvas = document.getElementById('jsKazoo04Icon');
            var ctx = canvas.getContext('2d');
            ctx.drawImage(image, 0, 0);
        };
    };
}

参考
Using files from web applications | MDN

Canvas を toDataURL で Data URL 形式の画像に変換する

解説はしていないがHTML/CSS/JavaScriptを駆使した画像の重ね方 でもできていたもの。
Canvas の描画内容を Data URL に変換して画像タグを貼り付けるだけです。
function generateKazoo04Icon(){
    var canvas = document.getElementById('jsKazoo04Icon');
    var ctx = canvas.getContext('2d');

    var dataURL = canvas.toDataURL('image/png');
    var imgTag = '<img src="' + dataURL + '" width="' + canvas.width + '" height="' + canvas.height + '" alt="かずー氏背景合成画像">';
    $('#jsKazoo04Icon').after(imgTag).remove();
    // ...
}

Data URL 形式の画像を Blob に変換して Blob URL 形式に変換

Canvas に toBlob もあるのですが、ほとんどのブラウザで実装されていないようです。
仕方ないので dataURL から Blob に変換する関数をかませて
window.URL.createObjectURL で Blob URL 形式に変換します。
function generateKazoo04Icon(){
    // ...
    var blob = Base64toBlob(dataURL);
    window.URL = window.URL || window.webkitURL;
    $('#jsDownload').attr("href", window.URL.createObjectURL(blob));
}

function Base64toBlob(_base64){
    var i;
    var tmp = _base64.split(',');
    var data = atob(tmp[1]);
    var mime = tmp[0].split(':')[1].split(';')[0];

    //var buff = new ArrayBuffer(data.length);
    //var arr = new Uint8Array(buff);
    var arr = new Uint8Array(data.length);
    for (i = 0; i < data.length; i++) {arr[i] = data.charCodeAt(i);}
    var blob = new Blob([arr], { type: mime });
    return blob;
}

参考
Canvasの画像(スクリーンショット)をローカルダウンロードします。 - jsdo.it - Share JavaScript, HTML5 and CSS
Base64をblobオブジェクトに変換します。 - jsdo.it - Share JavaScript, HTML5 and CSS
URL.createObjectURL - Web API Interfaces | MDN

だいたいオッケー、でも…

Drag and Drop による画像アップロードがわかりにくい点、
読み込んだ画像を移動、回転、拡大・縮小できない点がユーザー的に微妙ですね!
さぁあなたの Pull Request を待っています!

ちなみに「読み込んだ画像を移動、回転、拡大・縮小できない点」は
Fabric.js Javascript Canvas Libraryというものを使えば解決できます。

まだ時間をかければ実装はできますが、とりあえずリリースすることが大事ってばっちゃが言ってた。

2014/04/01

[Android]MediaActionSound ライクな SoundPoolManager

効果音の再生に使用する SoundPool が地味に使うのが面倒だったので
MediaActionSound のように使い勝手の良い
#load とか #play とかで R.raw.sound みたいな resId を渡すだけで
パッと再生できる SoundPoolManager クラスを作成しました。

MediaActionSound | Android Developers
SoundPoolManager.java
public class SoundPoolManager {

    private SoundPool mSoundPool;
    private Context mContext;
    SparseIntArray mLoadedSoundIds;
    private int mSoundIdToPlay;

    private static final int NUM_MEDIA_SOUND_STREAMS = 1;
    private static final int SOUND_NOT_LOADED = -1;

    public SoundPoolManager(Context context) {
        mContext = context;
        mSoundPool = new SoundPool(NUM_MEDIA_SOUND_STREAMS, AudioManager.STREAM_SYSTEM, 0);
        mSoundPool.setOnLoadCompleteListener(mLoadCompleteListener);
        mLoadedSoundIds = new SparseIntArray();
        mSoundIdToPlay = SOUND_NOT_LOADED;
    }

    /**
     * Preload a sound effect.
     * 
     * @param resId
     * @see #play(int)
     */
    public synchronized void load(int resId){
        if(mLoadedSoundIds.get(resId, SOUND_NOT_LOADED) == SOUND_NOT_LOADED){
            int loadedSoundId = mSoundPool.load(mContext, resId, 1);
            mLoadedSoundIds.put(resId, loadedSoundId);
        }
    }

    /**
     * Play a sound effect.
     * 
     * @param resId
     * @see #load(int)
     */
    public synchronized void play(int resId){
        int soundId = mLoadedSoundIds.get(resId, SOUND_NOT_LOADED);
        if(soundId == SOUND_NOT_LOADED){
            mSoundIdToPlay = mSoundPool.load(mContext, resId, 1);
            mLoadedSoundIds.put(resId, mSoundIdToPlay);
        }else{
            mSoundPool.play(soundId, 1.0f, 1.0f, 0, 0, 1.0f);
        }
    }

    private SoundPool.OnLoadCompleteListener mLoadCompleteListener = new SoundPool.OnLoadCompleteListener(){
        public void onLoadComplete(SoundPool soundPool, int sampleId, int status){
            if(status == 0){
                if(mSoundIdToPlay == sampleId){
                    soundPool.play(sampleId, 1.0f, 1.0f, 0, 0, 1.0f);
                    mSoundIdToPlay = SOUND_NOT_LOADED;
                }
            }else{
                Log.e(SoundPoolManager.class.getSimpleName(), "Unable to load sound for playback (status: " + status + ")");
            }
        }
    };

    /**
     * Free up all audio resources used by this SoundPoolManager instance. Do
     * not call any other methods on a SoundPoolManager instance after calling
     * release().
     */
    public void release(){
        if(mLoadedSoundIds == null){
            mLoadedSoundIds.clear();
            mLoadedSoundIds = null;
        }
        if(mSoundPool != null){
            mSoundPool.release();
            mSoundPool = null;
        }
    }

}