2013年1月28日月曜日

Androidのダウンロードマネージャで画像をダウンロードして完了を通知し、その通知をタップしたらその画像をギャラリーで開くには?

Androidのダウンロードマネージャで画像をダウンロードして完了を通知して、その通知をタップしたらその画像をギャラリーで開くにはどうしたらいいだろう?

この一連の流れを実現するのに色々と紆余曲折したのでまとめておこうと思う。

Androidのダウンロードマネージャで画像のダウンロードが完了すると、完了通知がブロードキャストインテントで飛んでくるので、これをすかさずキャッチ!

DownloadManagerBroadcastReceiverクラスの抜粋
/* 
 * ブロードキャスト受信時
 * 
 * @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
 */
@Override
public void onReceive(Context context, Intent intent) {

  //アクションによって処理を変える
  String action = intent.getAction();

  //ダウンロード完了の場合
  if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {

    //ダウンロードキューのID
    long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);

    //ダウンロードマネージャから各種情報を取得
    DownloadManager.Query query = new DownloadManager.Query();
    query.setFilterById(id);
    DownloadManager dlman = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
    Cursor cur = dlman.query(query);
    if (!cur.moveToFirst()) {
      Log.v(TAG, "ダウンロードマネージャでダウンロードがキャンセルされた");
      return;
    }
    int status = cur.getInt(cur.getColumnIndex(DownloadManager.COLUMN_STATUS));
    int reason = cur.getInt(cur.getColumnIndex(DownloadManager.COLUMN_REASON));

    // ダウンロードに成功した場合
    if (status == DownloadManager.STATUS_SUCCESSFUL) {
      Uri uri = Uri.parse(cur.getString(cur.getColumnIndex((DownloadManager.COLUMN_LOCAL_URI))));
      Log.v(getTag(), "ダウンロード成功: id=" + id + " status=" + status + " reason=" + reason + " uri=" + uri);
      Intent nIntent = new Intent(context, MediaScannerIntentService.class);
      nIntent.setAction(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
      nIntent.setData(uri);
      context.startService(nIntent);
      // ダウンロードしたファイルをAndroidデータベースに登録するためにMediaScannerIntentServiceにファイルスキャンを指示する。
      // MediaScannerConnection.scanFile()はBroadcastRevievierからは呼び出せないため。
    } // END if (status == DownloadManager.STATUS_SUCCESSFUL) {

    //カーソル閉じ
    cur.close();

  } // END if (DownloadManager.ACTION_DOWNLOAD_COMPLETE

} // END onReceive()

ダウンロードが成功した時点でその画像はファイルシステムに保存されてはいるけれど、ギャラリーなどで使える状態ではない。それは、Androidのデータベースにまだ登録されていないから。

で、この画像をAndroidのデータベースに登録するにはMediaScannerConnection.scanFile()を呼べば良いが、BroadcastReceiverでこのメソッドを呼ぶとエラーになる。

琴線探査: MediaScannerConnection.scanFile()はBroadcastRecieverから呼び出せない件

そこで、DownloadManagerBroadcastReceiverではひとまず完了通知の受信を行い、scanFile()を行うMediaScannerIntentServiceというサービスを作って、このサービスに対してインテントでファイルスキャンの指示を送信する。

MediaScannerIntentServiceの抜粋
/* 
 * インテント受信時
 * 
 * @see android.app.IntentService#onHandleIntent(android.content.Intent)
 */
@Override
protected void onHandleIntent(Intent intent) {

  Log.v(TAG, "インテント受信:" + intent);

  //アクションによって処理を変える
  String action = intent.getAction();

  //ファイルスキャン
  if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)) {
    Uri uri = intent.getData();
    Log.v(TAG, "ファイルスキャン開始: uri=" + uri);
    MediaScannerConnection.scanFile(getApplicationContext(),
        new String[] { uri.getPath() },
        new String[] { "image/*" },
        new MediaScannerConnection.OnScanCompletedListener() {
          public void onScanCompleted(String path, Uri uri) {
            Log.v(TAG, "ファイルスキャン終了: path=" + path + " uri=" + uri);
            notifyDownloadComplete(uri);
          }
        });
  } // END if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)) {

} // END onHandleIntent()


/**
 * @param uri 通知をタップした時に表示する画像のURI
 */
private void notifyDownloadComplete(Uri uri) {
  Log.v(TAG, "画像ダウンロード完了通知表示: uri=" + uri);
  Context context = getApplicationContext();
  Intent nIntent = new Intent(Intent.ACTION_VIEW);
  nIntent.setType("image/*");
  nIntent.setData(uri);
  PendingIntent pIntent = PendingIntent.getActivity(context, 0, nIntent, 0);
  Notification noti = new Notification();
  noti.icon = android.R.drawable.stat_sys_download_done;
  noti.tickerText = "画像のダウンロードが完了しました";
  noti.setLatestEventInfo(context, "画像ダウンロード完了", "タップして画像をチェック", pIntent);
  NotificationManager nman = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
  nman.notify(1, noti);
} // END notifyDownloadComplete()

onHandleIntent()でファイルスキャンを開始して、終了したらnotifyDownloadComplete()で実際に通知を表示する。「通知をタップしたら〜」の実現にはPendingIntentを使うのがポイントだ。


AndroidはIntentによる細かいクラスの連携で成り立っているんだねぇと改めて思う。unixコマンドがクラスで、パイプがIntent、みたいな。