2  *   ownCloud Android client application 
   4  *   @author David A. Velasco 
   5  *   Copyright (C) 2012 Bartek Przybylski 
   6  *   Copyright (C) 2015 ownCloud Inc. 
   8  *   This program is free software: you can redistribute it and/or modify 
   9  *   it under the terms of the GNU General Public License version 2, 
  10  *   as published by the Free Software Foundation. 
  12  *   This program is distributed in the hope that it will be useful, 
  13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  15  *   GNU General Public License for more details. 
  17  *   You should have received a copy of the GNU General Public License 
  18  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  22 package com
.owncloud
.android
.services
.observer
; 
  25 import java
.util
.HashMap
; 
  26 import java
.util
.Iterator
; 
  29 import android
.accounts
.Account
; 
  30 import android
.app
.Service
; 
  31 import android
.content
.BroadcastReceiver
; 
  32 import android
.content
.Context
; 
  33 import android
.content
.Intent
; 
  34 import android
.content
.IntentFilter
; 
  35 import android
.database
.Cursor
; 
  36 import android
.os
.IBinder
; 
  38 import com
.owncloud
.android
.MainApp
; 
  39 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  40 import com
.owncloud
.android
.datamodel
.OCFile
; 
  41 import com
.owncloud
.android
.db
.ProviderMeta
.ProviderTableMeta
; 
  42 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  43 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  44 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
  45 import com
.owncloud
.android
.utils
.FileStorageUtils
; 
  49  * Service keeping a list of {@link FolderObserver} instances that watch for local 
  50  * changes in favorite files (formerly known as kept-in-sync files) and try to 
  51  * synchronize them with the OC server as soon as possible. 
  53  * Tries to be alive as long as possible; that is the reason why stopSelf() is 
  56  * It is expected that the system eventually kills the service when runs low of 
  57  * memory. To minimize the impact of this, the service always returns 
  58  * Service.START_STICKY, and the later restart of the service is explicitly 
  59  * considered in {@link FileObserverService#onStartCommand(Intent, int, int)}. 
  61 public class FileObserverService 
extends Service 
{ 
  63     public final static String MY_NAME 
= FileObserverService
.class.getCanonicalName(); 
  64     public final static String ACTION_START_OBSERVE 
= MY_NAME 
+ ".action.START_OBSERVATION"; 
  65     public final static String ACTION_ADD_OBSERVED_FILE 
= MY_NAME 
+ ".action.ADD_OBSERVED_FILE"; 
  66     public final static String ACTION_DEL_OBSERVED_FILE 
= MY_NAME 
+ ".action.DEL_OBSERVED_FILE"; 
  68     private final static String ARG_FILE 
= "ARG_FILE"; 
  69     private final static String ARG_ACCOUNT 
= "ARG_ACCOUNT"; 
  71     private static String TAG 
= FileObserverService
.class.getSimpleName(); 
  73     private Map
<String
, FolderObserver
> mFolderObserversMap
; 
  74     private DownloadCompletedReceiver mDownloadReceiver
; 
  77      * Factory method to create intents that allow to start an ACTION_START_OBSERVE command. 
  79      * @param context   Android context of the caller component. 
  80      * @return          Intent that starts a command ACTION_START_OBSERVE when 
  81      *                  {@link Context#startService(Intent)} is called. 
  83     public static Intent 
makeInitIntent(Context context
) { 
  84         Intent i 
= new Intent(context
, FileObserverService
.class); 
  85         i
.setAction(ACTION_START_OBSERVE
); 
  90      * Factory method to create intents that allow to start or stop the 
  91      * observance of a file. 
  93      * @param context       Android context of the caller component. 
  94      * @param file          OCFile to start or stop to watch. 
  95      * @param account       OC account containing file. 
  96      * @param watchIt       'True' creates an intent to watch, 'false' an intent to stop watching. 
  97      * @return              Intent to start or stop the observance of a file through a call 
  98      *                      to {@link Context#startService(Intent)}. 
 100     public static Intent 
makeObservedFileIntent( 
 101             Context context
, OCFile file
, Account account
, boolean watchIt
) { 
 102         Intent intent 
= new Intent(context
, FileObserverService
.class); 
 103         intent
.setAction(watchIt ? FileObserverService
.ACTION_ADD_OBSERVED_FILE
 
 104                 : FileObserverService
.ACTION_DEL_OBSERVED_FILE
); 
 105         intent
.putExtra(FileObserverService
.ARG_FILE
, file
); 
 106         intent
.putExtra(FileObserverService
.ARG_ACCOUNT
, account
); 
 111      * Initialize the service.  
 114     public void onCreate() { 
 115         Log_OC
.d(TAG
, "onCreate"); 
 118         mDownloadReceiver 
= new DownloadCompletedReceiver(); 
 119         IntentFilter filter 
= new IntentFilter(); 
 120         filter
.addAction(FileDownloader
.getDownloadAddedMessage()); 
 121         filter
.addAction(FileDownloader
.getDownloadFinishMessage()); 
 122         registerReceiver(mDownloadReceiver
, filter
); 
 124         mFolderObserversMap 
= new HashMap
<String
, FolderObserver
>(); 
 131     public void onDestroy() { 
 132         Log_OC
.d(TAG
, "onDestroy - finishing observation of favorite files"); 
 134         unregisterReceiver(mDownloadReceiver
); 
 136         Iterator
<FolderObserver
> itOCFolder 
= mFolderObserversMap
.values().iterator(); 
 137         while (itOCFolder
.hasNext()) { 
 138             itOCFolder
.next().stopWatching(); 
 140         mFolderObserversMap
.clear(); 
 141         mFolderObserversMap 
= null
; 
 147      * This service cannot be bound. 
 150     public IBinder 
onBind(Intent intent
) { 
 155      * Handles requests to: 
 156      *  - (re)start watching                    (ACTION_START_OBSERVE) 
 157      *  - add an {@link OCFile} to be watched   (ATION_ADD_OBSERVED_FILE) 
 158      *  - stop observing an {@link OCFile}      (ACTION_DEL_OBSERVED_FILE)  
 161     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 162         Log_OC
.d(TAG
, "Starting command " + intent
); 
 164         if (intent 
== null 
|| ACTION_START_OBSERVE
.equals(intent
.getAction())) { 
 165             // NULL occurs when system tries to restart the service after its 
 166             // process was killed 
 168             return Service
.START_STICKY
; 
 170         } else if (ACTION_ADD_OBSERVED_FILE
.equals(intent
.getAction())) { 
 171             OCFile file 
= (OCFile
) intent
.getParcelableExtra(ARG_FILE
); 
 172             Account account 
= (Account
) intent
.getParcelableExtra(ARG_ACCOUNT
); 
 173             addObservedFile(file
, account
); 
 175         } else if (ACTION_DEL_OBSERVED_FILE
.equals(intent
.getAction())) { 
 176             removeObservedFile((OCFile
) intent
.getParcelableExtra(ARG_FILE
), 
 177                     (Account
) intent
.getParcelableExtra(ARG_ACCOUNT
)); 
 180             Log_OC
.e(TAG
, "Unknown action recieved; ignoring it: " + intent
.getAction()); 
 183         return Service
.START_STICKY
; 
 188      * Read from the local database the list of files that must to be kept 
 189      * synchronized and starts observers to monitor local changes on them. 
 191      * Updates the list of currently observed files if called multiple times. 
 193     private void startObservation() { 
 194         Log_OC
.d(TAG
, "Loading all kept-in-sync files from database to start watching them"); 
 196         // query for any favorite file in any OC account 
 197         Cursor cursorOnKeptInSync 
= getContentResolver().query( 
 198                 ProviderTableMeta
.CONTENT_URI
,  
 200                 ProviderTableMeta
.FILE_KEEP_IN_SYNC 
+ " = ?",  
 201                 new String
[] { String
.valueOf(1) },  
 205         if (cursorOnKeptInSync 
!= null
) { 
 207             if (cursorOnKeptInSync
.moveToFirst()) { 
 209                 String localPath 
= ""; 
 210                 String accountName 
= ""; 
 211                 Account account 
= null
; 
 213                     localPath 
= cursorOnKeptInSync
.getString(cursorOnKeptInSync
 
 214                             .getColumnIndex(ProviderTableMeta
.FILE_STORAGE_PATH
)); 
 215                     accountName 
= cursorOnKeptInSync
.getString(cursorOnKeptInSync
 
 216                             .getColumnIndex(ProviderTableMeta
.FILE_ACCOUNT_OWNER
)); 
 218                     account 
= new Account(accountName
, MainApp
.getAccountType()); 
 219                     if (!AccountUtils
.exists(account
, this) || localPath 
== null 
|| localPath
.length() <= 0) { 
 223                     addObservedFile(localPath
, account
); 
 225                 } while (cursorOnKeptInSync
.moveToNext()); 
 228             cursorOnKeptInSync
.close(); 
 231         // service does not stopSelf() ; that way it tries to be alive forever 
 237      * Registers the local copy of a remote file to be observed for local 
 238      * changes, an automatically updated in the ownCloud server. 
 240      * This method does NOT perform a {@link SynchronizeFileOperation} over the 
 243      * @param file      Object representing a remote file which local copy must be observed. 
 244      * @param account   OwnCloud account containing file. 
 246     private void addObservedFile(OCFile file
, Account account
) { 
 247         Log_OC
.v(TAG
, "Adding a file to be watched"); 
 250             Log_OC
.e(TAG
, "Trying to add a NULL file to observer"); 
 253         if (account 
== null
) { 
 254             Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer"); 
 258         String localPath 
= file
.getStoragePath(); 
 259         if (localPath 
== null 
|| localPath
.length() <= 0) { 
 260             // file downloading or to be downloaded for the first time 
 261             localPath 
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
); 
 264         addObservedFile(localPath
, account
); 
 272      * Registers a local file to be observed for changes. 
 274      * @param localPath     Absolute path in the local file system to the file to be observed. 
 275      * @param account       OwnCloud account associated to the local file. 
 277     private void addObservedFile(String localPath
, Account account
) { 
 278         File file 
= new File(localPath
); 
 279         String parentPath 
= file
.getParent(); 
 280         FolderObserver observer 
= mFolderObserversMap
.get(parentPath
); 
 281         if (observer 
== null
) { 
 282             observer 
= new FolderObserver(parentPath
, account
, getApplicationContext()); 
 283             mFolderObserversMap
.put(parentPath
, observer
); 
 284             Log_OC
.d(TAG
, "Observer added for parent folder " + parentPath 
+ "/"); 
 287         observer
.startWatching(file
.getName()); 
 288         Log_OC
.d(TAG
, "Added " + localPath 
+ " to list of observed children"); 
 293      * Unregisters the local copy of a remote file to be observed for local changes. 
 295      * @param file      Object representing a remote file which local copy must be not  
 297      * @param account   OwnCloud account containing file. 
 299     private void removeObservedFile(OCFile file
, Account account
) { 
 300         Log_OC
.v(TAG
, "Removing a file from being watched"); 
 303             Log_OC
.e(TAG
, "Trying to remove a NULL file"); 
 306         if (account 
== null
) { 
 307             Log_OC
.e(TAG
, "Trying to add a file with a NULL account to observer"); 
 311         String localPath 
= file
.getStoragePath(); 
 312         if (localPath 
== null 
|| localPath
.length() <= 0) { 
 313             localPath 
= FileStorageUtils
.getDefaultSavePathFor(account
.name
, file
); 
 316         removeObservedFile(localPath
); 
 321      * Unregisters a local file from being observed for changes. 
 323      * @param localPath     Absolute path in the local file system to the target file. 
 325     private void removeObservedFile(String localPath
) { 
 326         File file 
= new File(localPath
); 
 327         String parentPath 
= file
.getParent(); 
 328         FolderObserver observer 
= mFolderObserversMap
.get(parentPath
); 
 329         if (observer 
!= null
) { 
 330             observer
.stopWatching(file
.getName()); 
 331             if (observer
.isEmpty()) { 
 332                 mFolderObserversMap
.remove(parentPath
); 
 333                 Log_OC
.d(TAG
, "Observer removed for parent folder " + parentPath 
+ "/"); 
 337             Log_OC
.d(TAG
, "No observer to remove for path " + localPath
); 
 343      * Private receiver listening to events broadcasted by the {@link FileDownloader} service. 
 345      * Pauses and resumes the observance on registered files while being download, 
 346      * in order to avoid to unnecessary synchronizations. 
 348     private class DownloadCompletedReceiver 
extends BroadcastReceiver 
{ 
 351         public void onReceive(Context context
, Intent intent
) { 
 352             Log_OC
.d(TAG
, "Received broadcast intent " + intent
); 
 354             File downloadedFile 
= new File(intent
.getStringExtra(FileDownloader
.EXTRA_FILE_PATH
)); 
 355             String parentPath 
= downloadedFile
.getParent(); 
 356             FolderObserver observer 
= mFolderObserversMap
.get(parentPath
); 
 357             if (observer 
!= null
) { 
 358                 if (intent
.getAction().equals(FileDownloader
.getDownloadFinishMessage()) 
 359                         && downloadedFile
.exists()) { 
 360                     // no matter if the download was successful or not; the 
 361                     // file could be down anyway due to a former download or upload 
 362                     observer
.startWatching(downloadedFile
.getName()); 
 363                     Log_OC
.d(TAG
, "Resuming observance of " + downloadedFile
.getAbsolutePath()); 
 365                 } else if (intent
.getAction().equals(FileDownloader
.getDownloadAddedMessage())) { 
 366                     observer
.stopWatching(downloadedFile
.getName()); 
 367                     Log_OC
.d(TAG
, "Pausing observance of " + downloadedFile
.getAbsolutePath()); 
 371                 Log_OC
.d(TAG
, "No observer for path " + downloadedFile
.getAbsolutePath());