/*Google AdSense自動広告*/

2014年8月9日土曜日

Google Drive Android API を使用してファイルをアップロードする

Androidアプリの中で、Google DriveにアクセスするためにこのAPIを使用します。基本的にはGoogle本家サイトのやり方に従えばできますが、押さえておくべき事柄を記載します。
How to update files to Google Drive by API.

Google Drive Android API


 Getting Start から順次作業を進めます。


 Get an Android certificate and register your application


アプリごとにGoogle Driveにアクセスするための認証しなさい、という事です。認証はアプリに付けられた署名のフィンガープリントによって行います。注意すべきは、デバッグ用の署名と本番用の署名は違う、ということです。ご存知のように、アプリをEclipse等から起動した場合、デバッグ用の署名が自動で付けられます。これは本番用に(Android Tool⇛署名付きアプリをエクスポート)アプリを作成する際に使用する署名とは違うものです。
ですので、Google Developers Consoleに登録する際は、デバッグ用と本番用の2つを登録してしまったほうが良いと思います。
また、フィンガープリントをGUIで見ることができるEclipseプラグインが有ります。
http://keytool.sourceforge.net/


 Create a Proguard Exception


Proguardの設定をマニュアル通りに行っても、エラーが出てコンパイルできませんでした。他にも例外処理を加えなければならないとの情報もありましたが、結局Proguardをオフにしてコンパイルしました。


 Creating files


ここで書かれているコードは、空のファイルを作成するだけで、ローカルファイルをアップロードする訳ではありません。uploadFile(.., .., .., URI)なんてメソッドがあればいいのですが・・・。この情報が見つからなくて苦労しました。SDKを使う昔の方法や、POSTする方法しか見つからず。こんな方法しかないの?というコードになってしまいました。

ContentsResultCallback
 ↓
fileCallback
 ↓
ApiClientAsyncTaskにDriveFileを渡す
 ↓
DriveFileをMODE_WRITE_ONLYで開いてOutputStreamで書き込む


また、Drive上のファイル名やフォルダ名でアクセスすることは、このAPIでは不可能です。なんと不便なのでしょう!DriveIdという任意の文字列を介してアクセスすることになります。

例)
カメラで撮った画像をそのままGoogle Driveの指定したフォルダにアップロードするコードです。
フォルダのDriveIdは、下記のサンプルを参考に、ユーザーが指定するようにしました。
https://github.com/googledrive/android-demos/blob/master/src/com/google/android/gms/drive/sample/demo/PickFolderWithOpenerActivity.java

DriveIdはPreferenceに保存しておきます。もしフォルダが見つからない場合は、ルートフォルダに保存します。

また、PrefUtilは私の作ったクラスで、Preferenceを読み込み/書き込みするだけなので、一般的なコードに書き換えれば動作します。

(1) Activity(implements Camera.PictureCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener)
googleApiClient.connect()が成功するとonConnected()に飛びます。

static GoogleApiClient googleApiClient; 
static String uploadFileName;
static byte[] uploadData;
static DriveId folderDriveId;

@Override
 public void onPictureTaken(byte[] data, Camera camera) {
  try {
   String myPath = Environment.getExternalStorageDirectory().getPath() + "/SecEye";
   File dir = new File(myPath);
   if (!dir.exists()){
    dir.mkdirs();
   }

   Calendar calendar = Calendar.getInstance(Locale.getDefault());
   SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MMdd_HHmm_ss", Locale.getDefault());
   String myFileName = simpleDateFormat.format(calendar.getTime()) + ".jpg";
   String myFile = myPath + "/" + myFileName;
   
   BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(myFile));
   outputStream.write(data);
   outputStream.close();
             
      Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
      Uri contentUri = Uri.fromFile(new File(myFile));
      mediaScanIntent.setData(contentUri);
      this.sendBroadcast(mediaScanIntent);
      
      uploadFileName = myFileName;
    uploadData = data;
    googleApiClient = new GoogleApiClient.Builder(this)
   .addApi(Drive.API)
   .addScope(Drive.SCOPE_FILE)
   .addConnectionCallbacks(this)
   .addOnConnectionFailedListener(this)
   .build();
    googleApiClient.connect();
   
            
  } catch (Exception e) {
   Log.i("onPictureTaken", "file_error");

  } finally {
   Log.i("onPictureTaken", "End");
   
   finish();
  }
 } 
(2) API関係
onConnected()で指定したフォルダを探しに行きます。ただし、DriveIdが空欄だと落ちるので、その場合はルートフォルダにファイルを作成する道をとります。newContentsのCallbackを指定フォルダ用とルートフォルダ用に分けました。

フォルダが無事見つかった場合は、フォルダのDriveIdを利用してDriveFolderを作成し、そこにファイルを作成します。

ファイルが指定フォルダ又はルートフォルダに作成されると、fileCallbackへ進みます。

  // Google Drive Upload Tools
 @Override
 public void onConnectionFailed(ConnectionResult arg0) {
  finish();
 }

 @Override
 public void onConnected(Bundle connectionHint) {
  PreferenceUtil prefUtil = new PreferenceUtil(this);
  if (!prefUtil.GetString("folderDriveId").isEmpty()) {
   Drive.DriveApi.fetchDriveId(googleApiClient, prefUtil.GetString("folderDriveId"))
          .setResultCallback(idCallback);
  } else {
   Drive.DriveApi.newContents(googleApiClient)
             .setResultCallback(contentsOnRootCallback);
  }
 }

 @Override
 public void onConnectionSuspended(int cause) {
  //finish();
 } 
 
 final private ResultCallback<DriveIdResult> idCallback = new ResultCallback<DriveIdResult>() {
        @Override
        public void onResult(DriveIdResult result) {
            if (!result.getStatus().isSuccess()) {
             Drive.DriveApi.newContents(googleApiClient)
                 .setResultCallback(contentsOnRootCallback);
            } else {
             folderDriveId = result.getDriveId();
             Drive.DriveApi.newContents(googleApiClient)
                 .setResultCallback(contentsCallback);
            }
        }
    };
  
  // create in selected folder
 final private ResultCallback<ContentsResult> contentsCallback = new ResultCallback<ContentsResult>() {
        @Override
        public void onResult(ContentsResult result) {
            if (result.getStatus().isSuccess()) {
                MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                 .setTitle(uploadFileName)
                 .setMimeType("image/jpg")
                 .setStarred(false).build();
             
             DriveFolder folder = Drive.DriveApi.getFolder(googleApiClient, folderDriveId);
             folder.createFile(googleApiClient, changeSet, result.getContents())
                 .setResultCallback(fileCallback);
            }
        }
    };
    
 // create in root folder if (unknown folder/not selected)
    final private ResultCallback<ContentsResult> contentsOnRootCallback = new ResultCallback<ContentsResult>() {
        @Override
        public void onResult(ContentsResult result) {
            if (result.getStatus().isSuccess()) {
                MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                 .setTitle(uploadFileName)
                 .setMimeType("image/jpg")
                 .setStarred(false).build();
            
                Drive.DriveApi.getRootFolder(googleApiClient).createFile(googleApiClient, changeSet, result.getContents())
                 .setResultCallback(fileCallback);
            }
        }
    };
    
  // two contentsCallbacks return one fileCallback
    final private ResultCallback<DriveFileResult> fileCallback = new ResultCallback<DriveFileResult>() {
        @Override
        public void onResult(DriveFileResult result) {
            if (result.getStatus().isSuccess()) {
             DriveFile file = result.getDriveFile();
                new EditContentsAsyncTask(CameraActivity.this).execute(file);
            }            
        }
    };
    
(3) アップロード
作成した空のファイルを開き、OutputStreamを使って画像データをアップロードします。アップロードが終わったらGoogleApiClientをdisconnectしていますが、このタイミングは設計によりけりです。

    public class EditContentsAsyncTask extends ApiClientAsyncTask<DriveFile, Void, Boolean> {

        public EditContentsAsyncTask(Context context) {
            super(context);
        }

        @Override
        protected Boolean doInBackgroundConnected(DriveFile... args) {
            DriveFile file = args[0];
            try {
                ContentsResult contentsResult = file.openContents(
                        getGoogleApiClient(), DriveFile.MODE_WRITE_ONLY, null).await();
                if (!contentsResult.getStatus().isSuccess()) {
                    return false;
                }
                OutputStream outputStream = contentsResult.getContents().getOutputStream();
                outputStream.write(uploadData);
                
                com.google.android.gms.common.api.Status status = file.commitAndCloseContents(
                        getGoogleApiClient(), contentsResult.getContents()).await();
                
                try {
                 if (googleApiClient.isConnected() || googleApiClient.isConnecting()) {
                  googleApiClient.disconnect();
                 }
                } catch (Exception e) {
                 
                } finally {
                 googleApiClient = null;
                }
                
                return status.getStatus().isSuccess();
            } catch (IOException e) {
                
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean result) {}
    }

0 件のコメント:

コメントを投稿