1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012  Bartek Przybylski 
   3  *   Copyright (C) 2012-2013 ownCloud Inc. 
   5  *   This program is free software: you can redistribute it and/or modify 
   6  *   it under the terms of the GNU General Public License version 2, 
   7  *   as published by the Free Software Foundation. 
   9  *   This program is distributed in the hope that it will be useful, 
  10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  12  *   GNU General Public License for more details. 
  14  *   You should have received a copy of the GNU General Public License 
  15  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  19 package com
.owncloud
.android
; 
  22 import java
.util
.ArrayList
; 
  23 import java
.util
.HashMap
; 
  24 import java
.util
.LinkedList
; 
  25 import java
.util
.List
; 
  26 import java
.util
.Stack
; 
  27 import java
.util
.Vector
; 
  29 import com
.owncloud
.android
.R
; 
  30 import com
.owncloud
.android
.authentication
.AccountAuthenticator
; 
  31 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  32 import com
.owncloud
.android
.datamodel
.OCFile
; 
  33 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  35 import android
.accounts
.Account
; 
  36 import android
.accounts
.AccountManager
; 
  37 import android
.app
.AlertDialog
; 
  38 import android
.app
.AlertDialog
.Builder
; 
  39 import android
.app
.Dialog
; 
  40 import android
.app
.ListActivity
; 
  41 import android
.app
.ProgressDialog
; 
  42 import android
.content
.Context
; 
  43 import android
.content
.DialogInterface
; 
  44 import android
.content
.DialogInterface
.OnCancelListener
; 
  45 import android
.content
.DialogInterface
.OnClickListener
; 
  46 import android
.content
.Intent
; 
  47 import android
.database
.Cursor
; 
  48 import android
.net
.Uri
; 
  49 import android
.os
.Bundle
; 
  50 import android
.os
.Parcelable
; 
  51 import android
.provider
.MediaStore
.Audio
; 
  52 import android
.provider
.MediaStore
.Images
; 
  53 import android
.provider
.MediaStore
.Video
; 
  54 import android
.view
.View
; 
  55 import android
.view
.Window
; 
  56 import android
.widget
.AdapterView
; 
  57 import android
.widget
.AdapterView
.OnItemClickListener
; 
  58 import android
.widget
.Button
; 
  59 import android
.widget
.EditText
; 
  60 import android
.widget
.SimpleAdapter
; 
  61 import android
.widget
.Toast
; 
  65  * This can be used to upload things to an ownCloud instance. 
  67  * @author Bartek Przybylski 
  70 public class Uploader 
extends ListActivity 
implements OnItemClickListener
, android
.view
.View
.OnClickListener 
{ 
  71     private static final String TAG 
= "ownCloudUploader"; 
  73     private Account mAccount
; 
  74     private AccountManager mAccountManager
; 
  75     private Stack
<String
> mParents
; 
  76     private ArrayList
<Parcelable
> mStreamsToUpload
; 
  77     private boolean mCreateDir
; 
  78     private String mUploadPath
; 
  79     private FileDataStorageManager mStorageManager
; 
  82     private final static int DIALOG_NO_ACCOUNT 
= 0; 
  83     private final static int DIALOG_WAITING 
= 1; 
  84     private final static int DIALOG_NO_STREAM 
= 2; 
  85     private final static int DIALOG_MULTIPLE_ACCOUNT 
= 3; 
  87     private final static int REQUEST_CODE_SETUP_ACCOUNT 
= 0; 
  90     protected void onCreate(Bundle savedInstanceState
) { 
  91         super.onCreate(savedInstanceState
); 
  92         getWindow().requestFeature(Window
.FEATURE_NO_TITLE
); 
  93         mParents 
= new Stack
<String
>(); 
  95         if (prepareStreamsToUpload()) { 
  96             mAccountManager 
= (AccountManager
) getSystemService(Context
.ACCOUNT_SERVICE
); 
  97             Account
[] accounts 
= mAccountManager
.getAccountsByType(MainApp
.getAccountType()); 
  98             if (accounts
.length 
== 0) { 
  99                 Log_OC
.i(TAG
, "No ownCloud account is available"); 
 100                 showDialog(DIALOG_NO_ACCOUNT
); 
 101             } else if (accounts
.length 
> 1) { 
 102                 Log_OC
.i(TAG
, "More then one ownCloud is available"); 
 103                 showDialog(DIALOG_MULTIPLE_ACCOUNT
); 
 105                 mAccount 
= accounts
[0]; 
 106                 mStorageManager 
= new FileDataStorageManager(mAccount
, getContentResolver()); 
 107                 populateDirectoryList(); 
 110             showDialog(DIALOG_NO_STREAM
); 
 115     protected Dialog 
onCreateDialog(final int id
) { 
 116         final AlertDialog
.Builder builder 
= new Builder(this); 
 119             ProgressDialog pDialog 
= new ProgressDialog(this); 
 120             pDialog
.setIndeterminate(false
); 
 121             pDialog
.setCancelable(false
); 
 122             pDialog
.setMessage(getResources().getString(R
.string
.uploader_info_uploading
)); 
 124         case DIALOG_NO_ACCOUNT
: 
 125             builder
.setIcon(android
.R
.drawable
.ic_dialog_alert
); 
 126             builder
.setTitle(R
.string
.uploader_wrn_no_account_title
); 
 127             builder
.setMessage(String
.format(getString(R
.string
.uploader_wrn_no_account_text
), getString(R
.string
.app_name
))); 
 128             builder
.setCancelable(false
); 
 129             builder
.setPositiveButton(R
.string
.uploader_wrn_no_account_setup_btn_text
, new OnClickListener() { 
 131                 public void onClick(DialogInterface dialog
, int which
) { 
 132                     if (android
.os
.Build
.VERSION
.SDK_INT 
> android
.os
.Build
.VERSION_CODES
.ECLAIR_MR1
) { 
 133                         // using string value since in API7 this 
 134                         // constatn is not defined 
 135                         // in API7 < this constatant is defined in 
 136                         // Settings.ADD_ACCOUNT_SETTINGS 
 137                         // and Settings.EXTRA_AUTHORITIES 
 138                         Intent intent 
= new Intent(android
.provider
.Settings
.ACTION_ADD_ACCOUNT
); 
 139                         intent
.putExtra("authorities", new String
[] { MainApp
.getAuthTokenType() }); 
 140                         startActivityForResult(intent
, REQUEST_CODE_SETUP_ACCOUNT
); 
 142                         // since in API7 there is no direct call for 
 143                         // account setup, so we need to 
 144                         // show our own AccountSetupAcricity, get 
 145                         // desired results and setup 
 146                         // everything for ourself 
 147                         Intent intent 
= new Intent(getBaseContext(), AccountAuthenticator
.class); 
 148                         startActivityForResult(intent
, REQUEST_CODE_SETUP_ACCOUNT
); 
 152             builder
.setNegativeButton(R
.string
.uploader_wrn_no_account_quit_btn_text
, new OnClickListener() { 
 154                 public void onClick(DialogInterface dialog
, int which
) { 
 158             return builder
.create(); 
 159         case DIALOG_MULTIPLE_ACCOUNT
: 
 160             CharSequence ac
[] = new CharSequence
[mAccountManager
.getAccountsByType(MainApp
.getAccountType()).length
]; 
 161             for (int i 
= 0; i 
< ac
.length
; ++i
) { 
 162                 ac
[i
] = mAccountManager
.getAccountsByType(MainApp
.getAccountType())[i
].name
; 
 164             builder
.setTitle(R
.string
.common_choose_account
); 
 165             builder
.setItems(ac
, new OnClickListener() { 
 167                 public void onClick(DialogInterface dialog
, int which
) { 
 168                     mAccount 
= mAccountManager
.getAccountsByType(MainApp
.getAccountType())[which
]; 
 169                     mStorageManager 
= new FileDataStorageManager(mAccount
, getContentResolver()); 
 170                     populateDirectoryList(); 
 173             builder
.setCancelable(true
); 
 174             builder
.setOnCancelListener(new OnCancelListener() { 
 176                 public void onCancel(DialogInterface dialog
) { 
 181             return builder
.create(); 
 182         case DIALOG_NO_STREAM
: 
 183             builder
.setIcon(android
.R
.drawable
.ic_dialog_alert
); 
 184             builder
.setTitle(R
.string
.uploader_wrn_no_content_title
); 
 185             builder
.setMessage(R
.string
.uploader_wrn_no_content_text
); 
 186             builder
.setCancelable(false
); 
 187             builder
.setNegativeButton(R
.string
.common_cancel
, new OnClickListener() { 
 189                 public void onClick(DialogInterface dialog
, int which
) { 
 193             return builder
.create(); 
 195             throw new IllegalArgumentException("Unknown dialog id: " + id
); 
 199     class a 
implements OnClickListener 
{ 
 203         public a(String path
, EditText dirname
) { 
 209         public void onClick(DialogInterface dialog
, int which
) { 
 210             Uploader
.this.mUploadPath 
= mPath 
+ mDirname
.getText().toString(); 
 211             Uploader
.this.mCreateDir 
= true
; 
 217     public void onBackPressed() { 
 219         if (mParents
.size() <= 1) { 
 220             super.onBackPressed(); 
 224             populateDirectoryList(); 
 229     public void onItemClick(AdapterView
<?
> parent
, View view
, int position
, long id
) { 
 230         // click on folder in the list 
 231         Log_OC
.d(TAG
, "on item click"); 
 232         Vector
<OCFile
> tmpfiles 
= mStorageManager
.getFolderContent(mFile
); 
 233         if (tmpfiles
.size() <= 0) return; 
 235         Vector
<OCFile
> files 
= new Vector
<OCFile
>(); 
 236         for (OCFile f 
: tmpfiles
) 
 239         if (files
.size() < position
) { 
 240             throw new IndexOutOfBoundsException("Incorrect item selected"); 
 242         mParents
.push(files
.get(position
).getFileName()); 
 243         populateDirectoryList(); 
 247     public void onClick(View v
) { 
 250         case R
.id
.uploader_choose_folder
: 
 251             mUploadPath 
= "";   // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix 
 252             for (String p 
: mParents
) 
 253                 mUploadPath 
+= p 
+ OCFile
.PATH_SEPARATOR
; 
 254             Log_OC
.d(TAG
, "Uploading file to dir " + mUploadPath
); 
 260             throw new IllegalArgumentException("Wrong element clicked"); 
 265     protected void onActivityResult(int requestCode
, int resultCode
, Intent data
) { 
 266         super.onActivityResult(requestCode
, resultCode
, data
); 
 267         Log_OC
.i(TAG
, "result received. req: " + requestCode 
+ " res: " + resultCode
); 
 268         if (requestCode 
== REQUEST_CODE_SETUP_ACCOUNT
) { 
 269             dismissDialog(DIALOG_NO_ACCOUNT
); 
 270             if (resultCode 
== RESULT_CANCELED
) { 
 273             Account
[] accounts 
= mAccountManager
.getAccountsByType(MainApp
.getAuthTokenType()); 
 274             if (accounts
.length 
== 0) { 
 275                 showDialog(DIALOG_NO_ACCOUNT
); 
 277                 // there is no need for checking for is there more then one 
 278                 // account at this point 
 279                 // since account setup can set only one account at time 
 280                 mAccount 
= accounts
[0]; 
 281                 populateDirectoryList(); 
 286     private void populateDirectoryList() { 
 287         setContentView(R
.layout
.uploader_layout
); 
 289         String full_path 
= ""; 
 290         for (String a 
: mParents
) 
 291             full_path 
+= a 
+ "/"; 
 293         Log_OC
.d(TAG
, "Populating view with content of : " + full_path
); 
 295         mFile 
= mStorageManager
.getFileByPath(full_path
); 
 297             Vector
<OCFile
> files 
= mStorageManager
.getFolderContent(mFile
); 
 298             List
<HashMap
<String
, Object
>> data 
= new LinkedList
<HashMap
<String
,Object
>>(); 
 299             for (OCFile f 
: files
) { 
 300                 HashMap
<String
, Object
> h 
= new HashMap
<String
, Object
>(); 
 302                     h
.put("dirname", f
.getFileName()); 
 306             SimpleAdapter sa 
= new SimpleAdapter(this, 
 308                                                 R
.layout
.uploader_list_item_layout
, 
 309                                                 new String
[] {"dirname"}, 
 310                                                 new int[] {R
.id
.textView1
}); 
 312             Button btn 
= (Button
) findViewById(R
.id
.uploader_choose_folder
); 
 313             btn
.setOnClickListener(this); 
 314             getListView().setOnItemClickListener(this); 
 318     private boolean prepareStreamsToUpload() { 
 319         if (getIntent().getAction().equals(Intent
.ACTION_SEND
)) { 
 320             mStreamsToUpload 
= new ArrayList
<Parcelable
>(); 
 321             mStreamsToUpload
.add(getIntent().getParcelableExtra(Intent
.EXTRA_STREAM
)); 
 322         } else if (getIntent().getAction().equals(Intent
.ACTION_SEND_MULTIPLE
)) { 
 323             mStreamsToUpload 
= getIntent().getParcelableArrayListExtra(Intent
.EXTRA_STREAM
); 
 325         return (mStreamsToUpload 
!= null 
&& mStreamsToUpload
.get(0) != null
); 
 328     public void uploadFiles() { 
 330             //WebdavClient webdav = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); 
 332             ArrayList
<String
> local 
= new ArrayList
<String
>(); 
 333             ArrayList
<String
> remote 
= new ArrayList
<String
>(); 
 335             /* TODO - mCreateDir can never be true at this moment; we will replace wdc.createDirectory by CreateFolderOperation when that is fixed  
 336             WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); 
 337             // create last directory in path if necessary 
 339                 wdc.createDirectory(mUploadPath); 
 343             // this checks the mimeType  
 344             for (Parcelable mStream 
: mStreamsToUpload
) { 
 346                 Uri uri 
= (Uri
) mStream
; 
 348                     if (uri
.getScheme().equals("content")) { 
 350                        String mimeType 
= getContentResolver().getType(uri
); 
 352                        if (mimeType
.contains("image")) { 
 353                            String
[] CONTENT_PROJECTION 
= { Images
.Media
.DATA
, Images
.Media
.DISPLAY_NAME
, Images
.Media
.MIME_TYPE
, Images
.Media
.SIZE
}; 
 354                            Cursor c 
= getContentResolver().query(uri
, CONTENT_PROJECTION
, null
, null
, null
); 
 356                            int index 
= c
.getColumnIndex(Images
.Media
.DATA
); 
 357                            String data 
= c
.getString(index
); 
 359                            remote
.add(mUploadPath 
+ c
.getString(c
.getColumnIndex(Images
.Media
.DISPLAY_NAME
))); 
 362                        else if (mimeType
.contains("video")) { 
 363                            String
[] CONTENT_PROJECTION 
= { Video
.Media
.DATA
, Video
.Media
.DISPLAY_NAME
, Video
.Media
.MIME_TYPE
, Video
.Media
.SIZE
, Video
.Media
.DATE_MODIFIED 
}; 
 364                            Cursor c 
= getContentResolver().query(uri
, CONTENT_PROJECTION
, null
, null
, null
); 
 366                            int index 
= c
.getColumnIndex(Video
.Media
.DATA
); 
 367                            String data 
= c
.getString(index
); 
 369                            remote
.add(mUploadPath 
+ c
.getString(c
.getColumnIndex(Video
.Media
.DISPLAY_NAME
))); 
 372                        else if (mimeType
.contains("audio")) { 
 373                            String
[] CONTENT_PROJECTION 
= { Audio
.Media
.DATA
, Audio
.Media
.DISPLAY_NAME
, Audio
.Media
.MIME_TYPE
, Audio
.Media
.SIZE 
}; 
 374                            Cursor c 
= getContentResolver().query(uri
, CONTENT_PROJECTION
, null
, null
, null
); 
 376                            int index 
= c
.getColumnIndex(Audio
.Media
.DATA
); 
 377                            String data 
= c
.getString(index
); 
 379                            remote
.add(mUploadPath 
+ c
.getString(c
.getColumnIndex(Audio
.Media
.DISPLAY_NAME
))); 
 383                            String filePath 
= Uri
.decode(uri
.toString()).replace(uri
.getScheme() + "://", ""); 
 384                            // cut everything whats before mnt. It occured to me that sometimes apps send their name into the URI 
 385                            if (filePath
.contains("mnt")) { 
 386                               String splitedFilePath
[] = filePath
.split("/mnt"); 
 387                               filePath 
= splitedFilePath
[1]; 
 389                            final File file 
= new File(filePath
); 
 390                            local
.add(file
.getAbsolutePath()); 
 391                            remote
.add(mUploadPath 
+ file
.getName()); 
 394                     } else if (uri
.getScheme().equals("file")) { 
 395                         String filePath 
= Uri
.decode(uri
.toString()).replace(uri
.getScheme() + "://", ""); 
 396                         if (filePath
.contains("mnt")) { 
 397                            String splitedFilePath
[] = filePath
.split("/mnt"); 
 398                            filePath 
= splitedFilePath
[1]; 
 400                         final File file 
= new File(filePath
); 
 401                         local
.add(file
.getAbsolutePath()); 
 402                         remote
.add(mUploadPath 
+ file
.getName()); 
 405                         throw new SecurityException(); 
 409                     throw new SecurityException(); 
 412             Intent intent 
= new Intent(getApplicationContext(), FileUploader
.class); 
 413             intent
.putExtra(FileUploader
.KEY_UPLOAD_TYPE
, FileUploader
.UPLOAD_MULTIPLE_FILES
); 
 414             intent
.putExtra(FileUploader
.KEY_LOCAL_FILE
, local
.toArray(new String
[local
.size()])); 
 415             intent
.putExtra(FileUploader
.KEY_REMOTE_FILE
, remote
.toArray(new String
[remote
.size()])); 
 416             intent
.putExtra(FileUploader
.KEY_ACCOUNT
, mAccount
); 
 417             startService(intent
); 
 421         } catch (SecurityException e
) { 
 422             String message 
= String
.format(getString(R
.string
.uploader_error_forbidden_content
), getString(R
.string
.app_name
)); 
 423             Toast
.makeText(this, message
, Toast
.LENGTH_LONG
).show();