1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012 Bartek Przybylski 
   3  *   Copyright (C) 2012-2014 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
.services
.observer
; 
  22 import java
.util
.HashMap
; 
  23 import java
.util
.Iterator
; 
  26 import android
.accounts
.Account
; 
  27 import android
.app
.Service
; 
  28 import android
.content
.BroadcastReceiver
; 
  29 import android
.content
.Context
; 
  30 import android
.content
.Intent
; 
  31 import android
.content
.IntentFilter
; 
  32 import android
.database
.Cursor
; 
  33 import android
.media
.MediaScannerConnection
; 
  34 import android
.os
.IBinder
; 
  36 import com
.owncloud
.android
.MainApp
; 
  37 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  38 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  39 import com
.owncloud
.android
.datamodel
.OCFile
; 
  40 import com
.owncloud
.android
.db
.ProviderMeta
.ProviderTableMeta
; 
  41 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  42 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  43 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
  44 import com
.owncloud
.android
.utils
.FileStorageUtils
; 
  48  * Service keeping a list of {@link FolderObserver} instances that watch for local 
  49  * changes in favorite files (formerly known as kept-in-sync files) and try to 
  50  * synchronize them with the OC server as soon as possible. 
  52  * Tries to be alive as long as possible; that is the reason why stopSelf() is 
  55  * It is expected that the system eventually kills the service when runs low of 
  56  * memory. To minimize the impact of this, the service always returns 
  57  * Service.START_STICKY, and the later restart of the service is explicitly 
  58  * considered in {@link FileObserverService#onStartCommand(Intent, int, int)}. 
  60  * @author David A. Velasco 
  62 public class FileObserverService 
extends Service 
{ 
  64     public final static String MY_NAME 
= FileObserverService
.class.getCanonicalName(); 
  65     public final static String ACTION_START_OBSERVE 
= MY_NAME 
+ ".action.START_OBSERVATION"; 
  66     public final static String ACTION_ADD_OBSERVED_FILE 
= MY_NAME 
+ ".action.ADD_OBSERVED_FILE"; 
  67     public final static String ACTION_DEL_OBSERVED_FILE 
= MY_NAME 
+ ".action.DEL_OBSERVED_FILE"; 
  69     private final static String ARG_FILE 
= "ARG_FILE"; 
  70     private final static String ARG_ACCOUNT 
= "ARG_ACCOUNT"; 
  72     private static String TAG 
= FileObserverService
.class.getSimpleName(); 
  74     private Map
<String
, FolderObserver
> mFolderObserversMap
; 
  75     private DownloadCompletedReceiver mDownloadReceiver
; 
  78      * Factory method to create intents that allow to start an ACTION_START_OBSERVE command. 
  80      * @param context   Android context of the caller component. 
  81      * @return          Intent that starts a command ACTION_START_OBSERVE when 
  82      *                  {@link Context#startService(Intent)} is called. 
  84     public static Intent 
makeInitIntent(Context context
) { 
  85         Intent i 
= new Intent(context
, FileObserverService
.class); 
  86         i
.setAction(ACTION_START_OBSERVE
); 
  91      * Factory method to create intents that allow to start or stop the 
  92      * observance of a file. 
  94      * @param context       Android context of the caller component. 
  95      * @param file          OCFile to start or stop to watch. 
  96      * @param account       OC account containing file. 
  97      * @param watchIt       'True' creates an intent to watch, 'false' an intent to stop watching. 
  98      * @return              Intent to start or stop the observance of a file through a call 
  99      *                      to {@link Context#startService(Intent)}. 
 101     public static Intent 
makeObservedFileIntent( 
 102             Context context
, OCFile file
, Account account
, boolean watchIt
) { 
 103         Intent intent 
= new Intent(context
, FileObserverService
.class); 
 104         intent
.setAction(watchIt ? FileObserverService
.ACTION_ADD_OBSERVED_FILE
 
 105                 : FileObserverService
.ACTION_DEL_OBSERVED_FILE
); 
 106         intent
.putExtra(FileObserverService
.ARG_FILE
, file
); 
 107         intent
.putExtra(FileObserverService
.ARG_ACCOUNT
, account
); 
 112      * Initialize the service.  
 115     public void onCreate() { 
 116         Log_OC
.d(TAG
, "onCreate"); 
 119         mDownloadReceiver 
= new DownloadCompletedReceiver(); 
 120         IntentFilter filter 
= new IntentFilter(); 
 121         filter
.addAction(FileDownloader
.getDownloadAddedMessage()); 
 122         filter
.addAction(FileDownloader
.getDownloadFinishMessage()); 
 123         registerReceiver(mDownloadReceiver
, filter
); 
 125         mFolderObserversMap 
= new HashMap
<String
, FolderObserver
>(); 
 132     public void onDestroy() { 
 133         Log_OC
.d(TAG
, "onDestroy - finishing observation of favorite files"); 
 135         unregisterReceiver(mDownloadReceiver
); 
 137         Iterator
<FolderObserver
> itOCFolder 
= mFolderObserversMap
.values().iterator(); 
 138         while (itOCFolder
.hasNext()) { 
 139             itOCFolder
.next().stopWatching(); 
 141         mFolderObserversMap
.clear(); 
 142         mFolderObserversMap 
= null
; 
 148      * This service cannot be bound. 
 151     public IBinder 
onBind(Intent intent
) { 
 156      * Handles requests to: 
 157      *  - (re)start watching                    (ACTION_START_OBSERVE) 
 158      *  - add an {@link OCFile} to be watched   (ATION_ADD_OBSERVED_FILE) 
 159      *  - stop observing an {@link OCFile}      (ACTION_DEL_OBSERVED_FILE)  
 162     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 163         Log_OC
.d(TAG
, "Starting command " + intent
); 
 165         if (intent 
== null 
|| ACTION_START_OBSERVE
.equals(intent
.getAction())) { 
 166             // NULL occurs when system tries to restart the service after its 
 167             // process was killed 
 169             return Service
.START_STICKY
; 
 171         } else if (ACTION_ADD_OBSERVED_FILE
.equals(intent
.getAction())) { 
 172             OCFile file 
= (OCFile
) intent
.getParcelableExtra(ARG_FILE
); 
 173             Account account 
= (Account
) intent
.getParcelableExtra(ARG_ACCOUNT
); 
 174             addObservedFile(file
, account
); 
 176         } else if (ACTION_DEL_OBSERVED_FILE
.equals(intent
.getAction())) { 
 177             removeObservedFile((OCFile
) intent
.getParcelableExtra(ARG_FILE
), 
 178                     (Account
) intent
.getParcelableExtra(ARG_ACCOUNT
)); 
 181             Log_OC
.e(TAG
, "Unknown action recieved; ignoring it: " + intent
.getAction()); 
 184         return Service
.START_STICKY
; 
 189      * Read from the local database the list of files that must to be kept 
 190      * synchronized and starts observers to monitor local changes on them. 
 192      * Updates the list of currently observed files if called multiple times. 
 194     private void startObservation() { 
 195         Log_OC
.d(TAG
, "Loading all kept-in-sync files from database to start watching them"); 
 197         // query for any favorite file in any OC account 
 198         Cursor cursorOnKeptInSync 
= getContentResolver().query( 
 199                 ProviderTableMeta
.CONTENT_URI
,  
 201                 ProviderTableMeta
.FILE_KEEP_IN_SYNC 
+ " = ?",  
 202                 new String
[] { String
.valueOf(1) },  
 206         if (cursorOnKeptInSync 
!= null
) { 
 208             if (cursorOnKeptInSync
.moveToFirst()) { 
 210                 String localPath 
= ""; 
 211                 String accountName 
= ""; 
 212                 Account account 
= null
; 
 214                     localPath 
= cursorOnKeptInSync
.getString(cursorOnKeptInSync
 
 215                             .getColumnIndex(ProviderTableMeta
.FILE_STORAGE_PATH
)); 
 216                     accountName 
= cursorOnKeptInSync
.getString(cursorOnKeptInSync
 
 217                             .getColumnIndex(ProviderTableMeta
.FILE_ACCOUNT_OWNER
)); 
 219                     account 
= new Account(accountName
, MainApp
.getAccountType()); 
 220                     if (!AccountUtils
.exists(account
, this) || localPath 
== null 
|| localPath
.length() <= 0) { 
 224                     addObservedFile(localPath
, account
); 
 226                 } while (cursorOnKeptInSync
.moveToNext()); 
 229             cursorOnKeptInSync
.close(); 
 232         // service does not stopSelf() ; that way it tries to be alive forever 
 238      * Registers the local copy of a remote file to be observed for local 
 239      * changes, an automatically updated in the ownCloud server. 
 241      * This method does NOT perform a {@link SynchronizeFileOperation} over the 
 244      * @param file      Object representing a remote file which local copy must be observed. 
 245      * @param account   OwnCloud account containing file. 
 247     private void addObservedFile(OCFile file
, Account account
) { 
 248         Log_OC
.v(TAG
, "Adding a file to be watched"); 
 251             Log_OC
.e(TAG
, "Trying to add a NULL file to observer"); 
 254         if (account 
== null
) { 
 255             Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer"); 
 259         String localPath 
= file
.getStoragePath(); 
 260         if (localPath 
== null 
|| localPath
.length() <= 0) { 
 261             // file downloading or to be downloaded for the first time 
 262             localPath 
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
); 
 265         addObservedFile(localPath
, account
); 
 273      * Registers a local file to be observed for changes. 
 275      * @param localPath     Absolute path in the local file system to the file to be observed. 
 276      * @param account       OwnCloud account associated to the local file. 
 278     private void addObservedFile(String localPath
, Account account
) { 
 279         File file 
= new File(localPath
); 
 280         String parentPath 
= file
.getParent(); 
 281         FolderObserver observer 
= mFolderObserversMap
.get(parentPath
); 
 282         if (observer 
== null
) { 
 283             observer 
= new FolderObserver(parentPath
, account
, getApplicationContext()); 
 284             mFolderObserversMap
.put(parentPath
, observer
); 
 285             Log_OC
.d(TAG
, "Observer added for parent folder " + parentPath 
+ "/"); 
 288         observer
.startWatching(file
.getName()); 
 289         Log_OC
.d(TAG
, "Added " + localPath 
+ " to list of observed children"); 
 294      * Unregisters the local copy of a remote file to be observed for local changes. 
 296      * @param file      Object representing a remote file which local copy must be not  
 298      * @param account   OwnCloud account containing file. 
 300     private void removeObservedFile(OCFile file
, Account account
) { 
 301         Log_OC
.v(TAG
, "Removing a file from being watched"); 
 304             Log_OC
.e(TAG
, "Trying to remove a NULL file"); 
 307         if (account 
== null
) { 
 308             Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer"); 
 312         String localPath 
= file
.getStoragePath(); 
 313         if (localPath 
== null 
|| localPath
.length() <= 0) { 
 314             localPath 
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
); 
 317         removeObservedFile(localPath
); 
 322      * Unregisters a local file from being observed for changes. 
 324      * @param localPath     Absolute path in the local file system to the target file. 
 326     private void removeObservedFile(String localPath
) { 
 327         File file 
= new File(localPath
); 
 328         String parentPath 
= file
.getParent(); 
 329         FolderObserver observer 
= mFolderObserversMap
.get(parentPath
); 
 330         if (observer 
!= null
) { 
 331             observer
.stopWatching(file
.getName()); 
 332             if (observer
.isEmpty()) { 
 333                 mFolderObserversMap
.remove(parentPath
); 
 334                 Log_OC
.d(TAG
, "Observer removed for parent folder " + parentPath 
+ "/"); 
 338             Log_OC
.d(TAG
, "No observer to remove for path " + localPath
); 
 344      * Private receiver listening to events broadcasted by the {@link FileDownloader} service. 
 346      * Pauses and resumes the observance on registered files while being download, 
 347      * in order to avoid to unnecessary synchronizations. 
 349     private class DownloadCompletedReceiver 
extends BroadcastReceiver 
{ 
 352         public void onReceive(Context context
, Intent intent
) { 
 353             Log_OC
.d(TAG
, "Received broadcast intent " + intent
); 
 355             File downloadedFile 
= new File(intent
.getStringExtra(FileDownloader
.EXTRA_FILE_PATH
)); 
 356             String parentPath 
= downloadedFile
.getParent();             
 357             FolderObserver observer 
= mFolderObserversMap
.get(parentPath
); 
 358             if (observer 
!= null
) { 
 359                 if (intent
.getAction().equals(FileDownloader
.getDownloadFinishMessage()) 
 360                         && downloadedFile
.exists()) { 
 361                     // no matter if the download was successful or not; the 
 362                     // file could be down anyway due to a former download or upload 
 363                     observer
.startWatching(downloadedFile
.getName()); 
 364                     Log_OC
.d(TAG
, "Resuming observance of " + downloadedFile
.getAbsolutePath()); 
 366                 } else if (intent
.getAction().equals(FileDownloader
.getDownloadAddedMessage())) { 
 367                     observer
.stopWatching(downloadedFile
.getName()); 
 368                     Log_OC
.d(TAG
, "Pausing observance of " + downloadedFile
.getAbsolutePath()); 
 373                 if (downloadedFile
.exists()){ 
 374                     Log_OC
.d("mediaScan", "mediaScan : " + downloadedFile
.getAbsolutePath()); 
 375                     MediaScannerConnection
.scanFile(getApplicationContext(),  
 376                             new String
[]{downloadedFile
.getAbsolutePath()}, null
, null
); 
 379                 Log_OC
.d(TAG
, "No observer for path " + downloadedFile
.getAbsolutePath());