FileUploader.java 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author Bartek Przybylski
  5. * @author masensio
  6. * @author LukeOwnCloud
  7. * @author David A. Velasco
  8. * @author Chris Narkiewicz
  9. *
  10. * Copyright (C) 2012 Bartek Przybylski
  11. * Copyright (C) 2012-2016 ownCloud Inc.
  12. * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU General Public License version 2,
  16. * as published by the Free Software Foundation.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. */
  26. package com.owncloud.android.files.services;
  27. import android.accounts.Account;
  28. import android.accounts.AccountManager;
  29. import android.accounts.OnAccountsUpdateListener;
  30. import android.annotation.SuppressLint;
  31. import android.app.Notification;
  32. import android.app.NotificationManager;
  33. import android.app.PendingIntent;
  34. import android.app.Service;
  35. import android.content.Context;
  36. import android.content.Intent;
  37. import android.graphics.BitmapFactory;
  38. import android.os.Binder;
  39. import android.os.Build;
  40. import android.os.Handler;
  41. import android.os.HandlerThread;
  42. import android.os.IBinder;
  43. import android.os.Looper;
  44. import android.os.Message;
  45. import android.os.Parcelable;
  46. import android.os.Process;
  47. import android.util.Pair;
  48. import com.nextcloud.client.account.User;
  49. import com.nextcloud.client.account.UserAccountManager;
  50. import com.nextcloud.client.device.BatteryStatus;
  51. import com.nextcloud.client.device.PowerManagementService;
  52. import com.nextcloud.client.network.Connectivity;
  53. import com.nextcloud.client.network.ConnectivityService;
  54. import com.nextcloud.java.util.Optional;
  55. import com.owncloud.android.MainApp;
  56. import com.owncloud.android.R;
  57. import com.owncloud.android.authentication.AuthenticatorActivity;
  58. import com.owncloud.android.datamodel.FileDataStorageManager;
  59. import com.owncloud.android.datamodel.OCFile;
  60. import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  61. import com.owncloud.android.datamodel.UploadsStorageManager;
  62. import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
  63. import com.owncloud.android.db.OCUpload;
  64. import com.owncloud.android.db.UploadResult;
  65. import com.owncloud.android.lib.common.OwnCloudAccount;
  66. import com.owncloud.android.lib.common.OwnCloudClient;
  67. import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
  68. import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
  69. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  70. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  71. import com.owncloud.android.lib.common.utils.Log_OC;
  72. import com.owncloud.android.lib.resources.files.FileUtils;
  73. import com.owncloud.android.operations.UploadFileOperation;
  74. import com.owncloud.android.ui.activity.ConflictsResolveActivity;
  75. import com.owncloud.android.ui.activity.UploadListActivity;
  76. import com.owncloud.android.ui.notifications.NotificationUtils;
  77. import com.owncloud.android.utils.ErrorMessageAdapter;
  78. import com.owncloud.android.utils.theme.ThemeColorUtils;
  79. import java.io.File;
  80. import java.security.SecureRandom;
  81. import java.util.ArrayList;
  82. import java.util.HashMap;
  83. import java.util.List;
  84. import java.util.Map;
  85. import javax.annotation.Nullable;
  86. import javax.inject.Inject;
  87. import androidx.annotation.NonNull;
  88. import androidx.core.app.NotificationCompat;
  89. import androidx.localbroadcastmanager.content.LocalBroadcastManager;
  90. import dagger.android.AndroidInjection;
  91. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  92. /**
  93. * Service for uploading files. Invoke using context.startService(...).
  94. *
  95. * Files to be uploaded are stored persistently using {@link UploadsStorageManager}.
  96. *
  97. * On next invocation of {@link FileUploader} uploaded files which previously failed will be uploaded again until either
  98. * upload succeeded or a fatal error occurred.
  99. *
  100. * Every file passed to this service is uploaded. No filtering is performed. However, Intent keys (e.g., KEY_WIFI_ONLY)
  101. * are obeyed.
  102. */
  103. public class FileUploader extends Service
  104. implements OnDatatransferProgressListener, OnAccountsUpdateListener, UploadFileOperation.OnRenameListener {
  105. private static final String TAG = FileUploader.class.getSimpleName();
  106. private static final String UPLOADS_ADDED_MESSAGE = "UPLOADS_ADDED";
  107. private static final String UPLOAD_START_MESSAGE = "UPLOAD_START";
  108. private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
  109. public static final String EXTRA_UPLOAD_RESULT = "RESULT";
  110. public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
  111. public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
  112. public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
  113. public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
  114. public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
  115. private static final int FOREGROUND_SERVICE_ID = 411;
  116. public static final String KEY_FILE = "FILE";
  117. public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
  118. public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
  119. public static final String KEY_MIME_TYPE = "MIME_TYPE";
  120. /**
  121. * Call this Service with only this Intent key if all pending uploads are to be retried.
  122. */
  123. private static final String KEY_RETRY = "KEY_RETRY";
  124. // /**
  125. // * Call this Service with KEY_RETRY and KEY_RETRY_REMOTE_PATH to retry
  126. // * upload of file identified by KEY_RETRY_REMOTE_PATH.
  127. // */
  128. // private static final String KEY_RETRY_REMOTE_PATH = "KEY_RETRY_REMOTE_PATH";
  129. /**
  130. * Call this Service with KEY_RETRY and KEY_RETRY_UPLOAD to retry upload of file identified by KEY_RETRY_UPLOAD.
  131. */
  132. private static final String KEY_RETRY_UPLOAD = "KEY_RETRY_UPLOAD";
  133. /**
  134. * {@link Account} to which file is to be uploaded.
  135. */
  136. public static final String KEY_ACCOUNT = "ACCOUNT";
  137. /**
  138. * What {@link NameCollisionPolicy} to do when the file already exists on the remote.
  139. */
  140. public static final String KEY_NAME_COLLISION_POLICY = "KEY_NAME_COLLISION_POLICY";
  141. /**
  142. * Set to true if remote folder is to be created if it does not exist.
  143. */
  144. public static final String KEY_CREATE_REMOTE_FOLDER = "CREATE_REMOTE_FOLDER";
  145. /**
  146. * Key to signal what is the origin of the upload request
  147. */
  148. public static final String KEY_CREATED_BY = "CREATED_BY";
  149. public static final String KEY_WHILE_ON_WIFI_ONLY = "KEY_ON_WIFI_ONLY";
  150. /**
  151. * Set to true if upload is to performed only when phone is being charged.
  152. */
  153. public static final String KEY_WHILE_CHARGING_ONLY = "KEY_WHILE_CHARGING_ONLY";
  154. public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
  155. /**
  156. * Set to true if the HTTP library should disable automatic retries of uploads.
  157. */
  158. public static final String KEY_DISABLE_RETRIES = "DISABLE_RETRIES";
  159. public static final int LOCAL_BEHAVIOUR_COPY = 0;
  160. public static final int LOCAL_BEHAVIOUR_MOVE = 1;
  161. public static final int LOCAL_BEHAVIOUR_FORGET = 2;
  162. public static final int LOCAL_BEHAVIOUR_DELETE = 3;
  163. private Notification mNotification;
  164. private Looper mServiceLooper;
  165. private ServiceHandler mServiceHandler;
  166. private IBinder mBinder;
  167. private OwnCloudClient mUploadClient;
  168. private Account mCurrentAccount;
  169. private FileDataStorageManager mStorageManager;
  170. @Inject UserAccountManager accountManager;
  171. @Inject UploadsStorageManager mUploadsStorageManager;
  172. @Inject ConnectivityService connectivityService;
  173. @Inject PowerManagementService powerManagementService;
  174. @Inject LocalBroadcastManager localBroadcastManager;
  175. private IndexedForest<UploadFileOperation> mPendingUploads = new IndexedForest<>();
  176. /**
  177. * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent
  178. * upload!
  179. */
  180. private UploadFileOperation mCurrentUpload;
  181. private NotificationManager mNotificationManager;
  182. private NotificationCompat.Builder mNotificationBuilder;
  183. private int mLastPercent;
  184. @Override
  185. public void onRenameUpload() {
  186. mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload);
  187. sendBroadcastUploadStarted(mCurrentUpload);
  188. }
  189. /**
  190. * Service initialization
  191. */
  192. @Override
  193. public void onCreate() {
  194. super.onCreate();
  195. AndroidInjection.inject(this);
  196. Log_OC.d(TAG, "Creating service");
  197. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  198. HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND);
  199. thread.start();
  200. mServiceLooper = thread.getLooper();
  201. mServiceHandler = new ServiceHandler(mServiceLooper, this);
  202. mBinder = new FileUploaderBinder();
  203. NotificationCompat.Builder builder = new NotificationCompat.Builder(this).setContentTitle(
  204. getApplicationContext().getResources().getString(R.string.app_name))
  205. .setContentText(getApplicationContext().getResources().getString(R.string.foreground_service_upload))
  206. .setSmallIcon(R.drawable.notification_icon)
  207. .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.notification_icon))
  208. .setColor(ThemeColorUtils.primaryColor(getApplicationContext(), true));
  209. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  210. builder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD);
  211. }
  212. mNotification = builder.build();
  213. // TODO Add UploadResult.KILLED?
  214. int failedCounter = mUploadsStorageManager.failInProgressUploads(UploadResult.SERVICE_INTERRUPTED);
  215. if (failedCounter > 0) {
  216. resurrection();
  217. }
  218. // add AccountsUpdatedListener
  219. AccountManager am = AccountManager.get(getApplicationContext());
  220. am.addOnAccountsUpdatedListener(this, null, false);
  221. }
  222. /**
  223. * Service clean-up when restarted after being killed
  224. */
  225. private void resurrection() {
  226. // remove stucked notification
  227. mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
  228. }
  229. /**
  230. * Service clean up
  231. */
  232. @SuppressWarnings("PMD.NullAssignment")
  233. @Override
  234. public void onDestroy() {
  235. Log_OC.v(TAG, "Destroying service");
  236. mBinder = null;
  237. mServiceHandler = null;
  238. mServiceLooper.quit();
  239. mServiceLooper = null;
  240. if (mNotificationManager != null) {
  241. mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
  242. }
  243. mNotificationManager = null;
  244. // remove AccountsUpdatedListener
  245. AccountManager am = AccountManager.get(getApplicationContext());
  246. am.removeOnAccountsUpdatedListener(this);
  247. super.onDestroy();
  248. }
  249. /**
  250. * Entry point to add one or several files to the queue of uploads.
  251. *
  252. * New uploads are added calling to startService(), resulting in a call to this method. This ensures the service
  253. * will keep on working although the caller activity goes away.
  254. */
  255. @Override
  256. public int onStartCommand(Intent intent, int flags, int startId) {
  257. Log_OC.d(TAG, "Starting command with id " + startId);
  258. startForeground(FOREGROUND_SERVICE_ID, mNotification);
  259. if (intent == null) {
  260. Log_OC.e(TAG, "Intent is null");
  261. return Service.START_NOT_STICKY;
  262. }
  263. if (!intent.hasExtra(KEY_ACCOUNT)) {
  264. Log_OC.e(TAG, "Not enough information provided in intent");
  265. return Service.START_NOT_STICKY;
  266. }
  267. final Account account = intent.getParcelableExtra(KEY_ACCOUNT);
  268. if (account == null) {
  269. return Service.START_NOT_STICKY;
  270. }
  271. Optional<User> optionalUser = accountManager.getUser(account.name);
  272. if (!optionalUser.isPresent()) {
  273. return Service.START_NOT_STICKY;
  274. }
  275. final User user = optionalUser.get();
  276. boolean retry = intent.getBooleanExtra(KEY_RETRY, false);
  277. List<String> requestedUploads = new ArrayList<>();
  278. boolean onWifiOnly = intent.getBooleanExtra(KEY_WHILE_ON_WIFI_ONLY, false);
  279. boolean whileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, false);
  280. if (!retry) { // Start new uploads
  281. if (!(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
  282. Log_OC.e(TAG, "Not enough information provided in intent");
  283. return Service.START_NOT_STICKY;
  284. }
  285. Integer error = gatherAndStartNewUploads(intent, user, requestedUploads, onWifiOnly, whileChargingOnly);
  286. if (error != null) {
  287. return error;
  288. }
  289. } else { // Retry uploads
  290. if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_RETRY_UPLOAD)) {
  291. Log_OC.e(TAG, "Not enough information provided in intent: no KEY_RETRY_UPLOAD_KEY");
  292. return START_NOT_STICKY;
  293. }
  294. retryUploads(intent, user, requestedUploads);
  295. }
  296. if (requestedUploads.size() > 0) {
  297. Message msg = mServiceHandler.obtainMessage();
  298. msg.arg1 = startId;
  299. msg.obj = requestedUploads;
  300. mServiceHandler.sendMessage(msg);
  301. sendBroadcastUploadsAdded();
  302. }
  303. return Service.START_NOT_STICKY;
  304. }
  305. /**
  306. * Gather and start new uploads.
  307. *
  308. * @return A {@link Service} constant in case of error, {@code null} otherwise.
  309. */
  310. @Nullable
  311. private Integer gatherAndStartNewUploads(
  312. Intent intent,
  313. User user,
  314. List<String> requestedUploads,
  315. boolean onWifiOnly,
  316. boolean whileChargingOnly
  317. ) {
  318. String[] localPaths = null;
  319. String[] remotePaths = null;
  320. String[] mimeTypes = null;
  321. OCFile[] files = null;
  322. if (intent.hasExtra(KEY_FILE)) {
  323. Parcelable[] files_temp = intent.getParcelableArrayExtra(KEY_FILE);
  324. files = new OCFile[files_temp.length];
  325. System.arraycopy(files_temp, 0, files, 0, files_temp.length);
  326. } else {
  327. localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
  328. remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
  329. mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
  330. }
  331. if (intent.hasExtra(KEY_FILE) && files == null) {
  332. Log_OC.e(TAG, "Incorrect array for OCFiles provided in upload intent");
  333. return Service.START_NOT_STICKY;
  334. } else if (!intent.hasExtra(KEY_FILE)) {
  335. if (localPaths == null) {
  336. Log_OC.e(TAG, "Incorrect array for local paths provided in upload intent");
  337. return Service.START_NOT_STICKY;
  338. }
  339. if (remotePaths == null) {
  340. Log_OC.e(TAG, "Incorrect array for remote paths provided in upload intent");
  341. return Service.START_NOT_STICKY;
  342. }
  343. if (localPaths.length != remotePaths.length) {
  344. Log_OC.e(TAG, "Different number of remote paths and local paths!");
  345. return Service.START_NOT_STICKY;
  346. }
  347. files = new OCFile[localPaths.length];
  348. for (int i = 0; i < localPaths.length; i++) {
  349. files[i] = UploadFileOperation.obtainNewOCFileToUpload(
  350. remotePaths[i],
  351. localPaths[i],
  352. mimeTypes != null ? mimeTypes[i] : null
  353. );
  354. if (files[i] == null) {
  355. Log_OC.e(TAG, "obtainNewOCFileToUpload() returned null for remotePaths[i]:" + remotePaths[i]
  356. + " and localPaths[i]:" + localPaths[i]);
  357. return Service.START_NOT_STICKY;
  358. }
  359. }
  360. }
  361. // at this point variable "OCFile[] files" is loaded correctly.
  362. NameCollisionPolicy nameCollisionPolicy = (NameCollisionPolicy) intent.getSerializableExtra(KEY_NAME_COLLISION_POLICY);
  363. if (nameCollisionPolicy == null) {
  364. nameCollisionPolicy = NameCollisionPolicy.DEFAULT;
  365. }
  366. int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET);
  367. boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false);
  368. int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER);
  369. boolean disableRetries = intent.getBooleanExtra(KEY_DISABLE_RETRIES, true);
  370. try {
  371. for (OCFile file : files) {
  372. startNewUpload(
  373. user,
  374. requestedUploads,
  375. onWifiOnly,
  376. whileChargingOnly,
  377. nameCollisionPolicy,
  378. localAction,
  379. isCreateRemoteFolder,
  380. createdBy,
  381. file,
  382. disableRetries
  383. );
  384. }
  385. } catch (IllegalArgumentException e) {
  386. Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
  387. return START_NOT_STICKY;
  388. } catch (IllegalStateException e) {
  389. Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
  390. return START_NOT_STICKY;
  391. } catch (Exception e) {
  392. Log_OC.e(TAG, "Unexpected exception while processing upload intent", e);
  393. return START_NOT_STICKY;
  394. }
  395. return null;
  396. }
  397. /**
  398. * Start a new {@link UploadFileOperation}.
  399. */
  400. @SuppressLint("SdCardPath")
  401. private void startNewUpload(
  402. User user,
  403. List<String> requestedUploads,
  404. boolean onWifiOnly,
  405. boolean whileChargingOnly,
  406. NameCollisionPolicy nameCollisionPolicy,
  407. int localAction,
  408. boolean isCreateRemoteFolder,
  409. int createdBy,
  410. OCFile file,
  411. boolean disableRetries
  412. ) {
  413. if (file.getStoragePath().startsWith("/data/data/")) {
  414. Log_OC.d(TAG, "Upload from sensitive path is not allowed");
  415. return;
  416. }
  417. OCUpload ocUpload = new OCUpload(file, user.toPlatformAccount());
  418. ocUpload.setFileSize(file.getFileLength());
  419. ocUpload.setNameCollisionPolicy(nameCollisionPolicy);
  420. ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
  421. ocUpload.setCreatedBy(createdBy);
  422. ocUpload.setLocalAction(localAction);
  423. ocUpload.setUseWifiOnly(onWifiOnly);
  424. ocUpload.setWhileChargingOnly(whileChargingOnly);
  425. ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
  426. UploadFileOperation newUpload = new UploadFileOperation(
  427. mUploadsStorageManager,
  428. connectivityService,
  429. powerManagementService,
  430. user,
  431. file,
  432. ocUpload,
  433. nameCollisionPolicy,
  434. localAction,
  435. this,
  436. onWifiOnly,
  437. whileChargingOnly,
  438. disableRetries,
  439. new FileDataStorageManager(user.toPlatformAccount(), getContentResolver())
  440. );
  441. newUpload.setCreatedBy(createdBy);
  442. if (isCreateRemoteFolder) {
  443. newUpload.setRemoteFolderToBeCreated();
  444. }
  445. newUpload.addDataTransferProgressListener(this);
  446. newUpload.addDataTransferProgressListener((FileUploaderBinder) mBinder);
  447. newUpload.addRenameUploadListener(this);
  448. Pair<String, String> putResult = mPendingUploads.putIfAbsent(
  449. user.getAccountName(),
  450. file.getRemotePath(),
  451. newUpload
  452. );
  453. if (putResult != null) {
  454. requestedUploads.add(putResult.first);
  455. // Save upload in database
  456. long id = mUploadsStorageManager.storeUpload(ocUpload);
  457. newUpload.setOCUploadId(id);
  458. }
  459. }
  460. /**
  461. * Retries a list of uploads.
  462. */
  463. private void retryUploads(Intent intent, User user, List<String> requestedUploads) {
  464. boolean onWifiOnly;
  465. boolean whileChargingOnly;
  466. OCUpload upload = intent.getParcelableExtra(KEY_RETRY_UPLOAD);
  467. onWifiOnly = upload.isUseWifiOnly();
  468. whileChargingOnly = upload.isWhileChargingOnly();
  469. UploadFileOperation newUpload = new UploadFileOperation(
  470. mUploadsStorageManager,
  471. connectivityService,
  472. powerManagementService,
  473. user,
  474. null,
  475. upload,
  476. upload.getNameCollisionPolicy(),
  477. upload.getLocalAction(),
  478. this,
  479. onWifiOnly,
  480. whileChargingOnly,
  481. true,
  482. new FileDataStorageManager(user.toPlatformAccount(), getContentResolver())
  483. );
  484. newUpload.addDataTransferProgressListener(this);
  485. newUpload.addDataTransferProgressListener((FileUploaderBinder) mBinder);
  486. newUpload.addRenameUploadListener(this);
  487. Pair<String, String> putResult = mPendingUploads.putIfAbsent(
  488. user.getAccountName(),
  489. upload.getRemotePath(),
  490. newUpload
  491. );
  492. if (putResult != null) {
  493. String uploadKey = putResult.first;
  494. requestedUploads.add(uploadKey);
  495. // Update upload in database
  496. upload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
  497. mUploadsStorageManager.updateUpload(upload);
  498. }
  499. }
  500. /**
  501. * Provides a binder object that clients can use to perform operations on the queue of uploads, excepting the
  502. * addition of new files.
  503. *
  504. * Implemented to perform cancellation, pause and resume of existing uploads.
  505. */
  506. @Override
  507. public IBinder onBind(Intent intent) {
  508. return mBinder;
  509. }
  510. /**
  511. * Called when ALL the bound clients were onbound.
  512. */
  513. @Override
  514. public boolean onUnbind(Intent intent) {
  515. ((FileUploaderBinder) mBinder).clearListeners();
  516. return false; // not accepting rebinding (default behaviour)
  517. }
  518. @Override
  519. public void onAccountsUpdated(Account[] accounts) {
  520. // Review current upload, and cancel it if its account doesn't exist
  521. if (mCurrentUpload != null && !accountManager.exists(mCurrentUpload.getAccount())) {
  522. mCurrentUpload.cancel(ResultCode.ACCOUNT_NOT_FOUND);
  523. }
  524. // The rest of uploads are cancelled when they try to start
  525. }
  526. /**
  527. * Core upload method: sends the file(s) to upload
  528. *
  529. * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
  530. */
  531. public void uploadFile(String uploadKey) {
  532. mCurrentUpload = mPendingUploads.get(uploadKey);
  533. if (mCurrentUpload != null) {
  534. /// Check account existence
  535. if (!accountManager.exists(mCurrentUpload.getAccount())) {
  536. Log_OC.w(TAG, "Account " + mCurrentUpload.getAccount().name +
  537. " does not exist anymore -> cancelling all its uploads");
  538. cancelUploadsForAccount(mCurrentUpload.getAccount());
  539. return;
  540. }
  541. /// OK, let's upload
  542. mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload);
  543. notifyUploadStart(mCurrentUpload);
  544. sendBroadcastUploadStarted(mCurrentUpload);
  545. RemoteOperationResult uploadResult = null;
  546. try {
  547. /// prepare client object to send the request to the ownCloud server
  548. if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentUpload.getAccount())) {
  549. mCurrentAccount = mCurrentUpload.getAccount();
  550. mStorageManager = new FileDataStorageManager(mCurrentAccount, getContentResolver());
  551. } // else, reuse storage manager from previous operation
  552. // always get client from client manager, to get fresh credentials in case of update
  553. OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this);
  554. mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this);
  555. // // If parent folder is encrypted, upload file encrypted
  556. // OCFile parent = mStorageManager.getFileByPath(mCurrentUpload.getFile().getParentRemotePath());
  557. // if (parent.isEncrypted()) {
  558. // UploadEncryptedFileOperation uploadEncryptedFileOperation =
  559. // new UploadEncryptedFileOperation(parent, mCurrentUpload);
  560. //
  561. // uploadResult = uploadEncryptedFileOperation.execute(mUploadClient, mStorageManager);
  562. // } else {
  563. /// perform the regular upload
  564. uploadResult = mCurrentUpload.execute(mUploadClient);
  565. // }
  566. } catch (Exception e) {
  567. Log_OC.e(TAG, "Error uploading", e);
  568. uploadResult = new RemoteOperationResult(e);
  569. } finally {
  570. Pair<UploadFileOperation, String> removeResult;
  571. if (mCurrentUpload.wasRenamed()) {
  572. removeResult = mPendingUploads.removePayload(
  573. mCurrentAccount.name,
  574. mCurrentUpload.getOldFile().getRemotePath()
  575. );
  576. // TODO: grant that name is also updated for mCurrentUpload.getOCUploadId
  577. } else {
  578. removeResult = mPendingUploads.removePayload(mCurrentAccount.name,
  579. mCurrentUpload.getDecryptedRemotePath());
  580. }
  581. mUploadsStorageManager.updateDatabaseUploadResult(uploadResult, mCurrentUpload);
  582. /// notify result
  583. notifyUploadResult(mCurrentUpload, uploadResult);
  584. sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second);
  585. }
  586. // generate new Thumbnail
  587. Optional<User> user = getCurrentUser();
  588. if (user.isPresent()) {
  589. final ThumbnailsCacheManager.ThumbnailGenerationTask task =
  590. new ThumbnailsCacheManager.ThumbnailGenerationTask(mStorageManager, user.get());
  591. File file = new File(mCurrentUpload.getOriginalStoragePath());
  592. String remoteId = mCurrentUpload.getFile().getRemoteId();
  593. task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, remoteId));
  594. }
  595. }
  596. }
  597. /**
  598. * Convert current account to user. This is a temporary workaround until
  599. * service is migrated to new user model.
  600. *
  601. * @return Optional {@link User}
  602. */
  603. private Optional<User> getCurrentUser() {
  604. if (mCurrentAccount == null) {
  605. return Optional.empty();
  606. } else {
  607. return accountManager.getUser(mCurrentAccount.name);
  608. }
  609. }
  610. /**
  611. * Creates a status notification to show the upload progress
  612. *
  613. * @param upload Upload operation starting.
  614. */
  615. private void notifyUploadStart(UploadFileOperation upload) {
  616. // / create status notification with a progress bar
  617. mLastPercent = 0;
  618. mNotificationBuilder = NotificationUtils.newNotificationBuilder(this);
  619. mNotificationBuilder
  620. .setOngoing(true)
  621. .setSmallIcon(R.drawable.notification_icon)
  622. .setTicker(getString(R.string.uploader_upload_in_progress_ticker))
  623. .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker))
  624. .setProgress(100, 0, false)
  625. .setContentText(
  626. String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())
  627. );
  628. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  629. mNotificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD);
  630. }
  631. /// includes a pending intent in the notification showing the details
  632. Intent intent = UploadListActivity.createIntent(upload.getFile(),
  633. upload.getAccount(),
  634. Intent.FLAG_ACTIVITY_CLEAR_TOP,
  635. this);
  636. mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this,
  637. (int) System.currentTimeMillis(),
  638. intent,
  639. 0)
  640. );
  641. if (!upload.isInstantPicture() && !upload.isInstantVideo()) {
  642. if (mNotificationManager == null) {
  643. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  644. }
  645. mNotificationManager.notify(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
  646. } // else wait until the upload really start (onTransferProgress is called), so that if it's discarded
  647. // due to lack of Wifi, no notification is shown
  648. // TODO generalize for automated uploads
  649. }
  650. /**
  651. * Callback method to update the progress bar in the status notification
  652. */
  653. @Override
  654. public void onTransferProgress(
  655. long progressRate,
  656. long totalTransferredSoFar,
  657. long totalToTransfer,
  658. String filePath
  659. ) {
  660. int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
  661. if (percent != mLastPercent) {
  662. mNotificationBuilder.setProgress(100, percent, false);
  663. String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
  664. String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
  665. mNotificationBuilder.setContentText(text);
  666. if (mNotificationManager == null) {
  667. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  668. }
  669. mNotificationManager.notify(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
  670. }
  671. mLastPercent = percent;
  672. }
  673. /**
  674. * Updates the status notification with the result of an upload operation.
  675. *
  676. * @param uploadResult Result of the upload operation.
  677. * @param upload Finished upload operation
  678. */
  679. @SuppressFBWarnings("DMI")
  680. private void notifyUploadResult(UploadFileOperation upload, RemoteOperationResult uploadResult) {
  681. Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
  682. // cancelled operation or success -> silent removal of progress notification
  683. if (mNotificationManager == null) {
  684. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  685. }
  686. // Only notify if the upload fails
  687. if (!uploadResult.isCancelled() &&
  688. !uploadResult.isSuccess() &&
  689. !ResultCode.LOCAL_FILE_NOT_FOUND.equals(uploadResult.getCode()) &&
  690. !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_WIFI) &&
  691. !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_CHARGING) &&
  692. !uploadResult.getCode().equals(ResultCode.DELAYED_IN_POWER_SAVE_MODE) &&
  693. !uploadResult.getCode().equals(ResultCode.LOCK_FAILED)) {
  694. int tickerId = R.string.uploader_upload_failed_ticker;
  695. String content;
  696. // check credentials error
  697. boolean needsToUpdateCredentials = uploadResult.getCode() == ResultCode.UNAUTHORIZED;
  698. if (needsToUpdateCredentials) {
  699. tickerId = R.string.uploader_upload_failed_credentials_error;
  700. } else if (uploadResult.getCode() == ResultCode.SYNC_CONFLICT) { // check file conflict
  701. tickerId = R.string.uploader_upload_failed_sync_conflict_error;
  702. }
  703. mNotificationBuilder
  704. .setTicker(getString(tickerId))
  705. .setContentTitle(getString(tickerId))
  706. .setAutoCancel(true)
  707. .setOngoing(false)
  708. .setProgress(0, 0, false);
  709. content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, upload, getResources());
  710. if (needsToUpdateCredentials) {
  711. // let the user update credentials with one click
  712. Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
  713. updateAccountCredentials.putExtra(
  714. AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount()
  715. );
  716. updateAccountCredentials.putExtra(
  717. AuthenticatorActivity.EXTRA_ACTION,
  718. AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
  719. );
  720. updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  721. updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
  722. updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
  723. mNotificationBuilder.setContentIntent(PendingIntent.getActivity(
  724. this,
  725. (int) System.currentTimeMillis(),
  726. updateAccountCredentials,
  727. PendingIntent.FLAG_ONE_SHOT
  728. ));
  729. } else {
  730. Intent intent;
  731. if (uploadResult.getCode().equals(ResultCode.SYNC_CONFLICT)) {
  732. intent = ConflictsResolveActivity.createIntent(upload.getFile(),
  733. upload.getAccount(),
  734. upload.getOCUploadId(),
  735. Intent.FLAG_ACTIVITY_CLEAR_TOP,
  736. this);
  737. } else {
  738. intent = UploadListActivity.createIntent(upload.getFile(),
  739. upload.getAccount(),
  740. Intent.FLAG_ACTIVITY_CLEAR_TOP,
  741. this);
  742. }
  743. mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this,
  744. (int) System.currentTimeMillis(),
  745. intent,
  746. 0)
  747. );
  748. }
  749. mNotificationBuilder.setContentText(content);
  750. if (!uploadResult.isSuccess()) {
  751. mNotificationManager.notify((new SecureRandom()).nextInt(), mNotificationBuilder.build());
  752. }
  753. }
  754. }
  755. /**
  756. * Sends a broadcast in order to the interested activities can update their view
  757. *
  758. * TODO - no more broadcasts, replace with a callback to subscribed listeners
  759. */
  760. private void sendBroadcastUploadsAdded() {
  761. Intent start = new Intent(getUploadsAddedMessage());
  762. // nothing else needed right now
  763. start.setPackage(getPackageName());
  764. localBroadcastManager.sendBroadcast(start);
  765. }
  766. /**
  767. * Sends a broadcast in order to the interested activities can update their view
  768. *
  769. * TODO - no more broadcasts, replace with a callback to subscribed listeners
  770. *
  771. * @param upload Finished upload operation
  772. */
  773. private void sendBroadcastUploadStarted(UploadFileOperation upload) {
  774. Intent start = new Intent(getUploadStartMessage());
  775. start.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
  776. start.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
  777. start.putExtra(ACCOUNT_NAME, upload.getAccount().name);
  778. start.setPackage(getPackageName());
  779. localBroadcastManager.sendBroadcast(start);
  780. }
  781. /**
  782. * Sends a broadcast in order to the interested activities can update their view
  783. *
  784. * TODO - no more broadcasts, replace with a callback to subscribed listeners
  785. *
  786. * @param upload Finished upload operation
  787. * @param uploadResult Result of the upload operation
  788. * @param unlinkedFromRemotePath Path in the uploads tree where the upload was unlinked from
  789. */
  790. private void sendBroadcastUploadFinished(
  791. UploadFileOperation upload,
  792. RemoteOperationResult uploadResult,
  793. String unlinkedFromRemotePath
  794. ) {
  795. Intent end = new Intent(getUploadFinishMessage());
  796. end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
  797. // path, after
  798. // possible
  799. // automatic
  800. // renaming
  801. if (upload.wasRenamed()) {
  802. end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath());
  803. }
  804. end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
  805. end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
  806. end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
  807. if (unlinkedFromRemotePath != null) {
  808. end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
  809. }
  810. end.setPackage(getPackageName());
  811. localBroadcastManager.sendBroadcast(end);
  812. }
  813. /**
  814. * Remove and 'forgets' pending uploads of an account.
  815. *
  816. * @param account Account which uploads will be cancelled
  817. */
  818. private void cancelUploadsForAccount(Account account) {
  819. mPendingUploads.remove(account.name);
  820. mUploadsStorageManager.removeUploads(account.name);
  821. }
  822. /**
  823. * Upload a new file
  824. */
  825. public static void uploadNewFile(
  826. Context context,
  827. Account account,
  828. String localPath,
  829. String remotePath,
  830. int behaviour,
  831. String mimeType,
  832. boolean createRemoteFile,
  833. int createdBy,
  834. boolean requiresWifi,
  835. boolean requiresCharging,
  836. NameCollisionPolicy nameCollisionPolicy
  837. ) {
  838. uploadNewFile(
  839. context,
  840. account,
  841. new String[]{localPath},
  842. new String[]{remotePath},
  843. new String[]{mimeType},
  844. behaviour,
  845. createRemoteFile,
  846. createdBy,
  847. requiresWifi,
  848. requiresCharging,
  849. nameCollisionPolicy
  850. );
  851. }
  852. /**
  853. * Upload multiple new files
  854. */
  855. public static void uploadNewFile(
  856. Context context,
  857. Account account,
  858. String[] localPaths,
  859. String[] remotePaths,
  860. String[] mimeTypes,
  861. Integer behaviour,
  862. Boolean createRemoteFolder,
  863. int createdBy,
  864. boolean requiresWifi,
  865. boolean requiresCharging,
  866. NameCollisionPolicy nameCollisionPolicy
  867. ) {
  868. Intent intent = new Intent(context, FileUploader.class);
  869. intent.putExtra(FileUploader.KEY_ACCOUNT, account);
  870. intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths);
  871. intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
  872. intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes);
  873. intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
  874. intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
  875. intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
  876. intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
  877. intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
  878. intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
  879. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  880. context.startForegroundService(intent);
  881. } else {
  882. context.startService(intent);
  883. }
  884. }
  885. /**
  886. * Upload and overwrite an already uploaded file with disabled retries
  887. */
  888. public static void uploadUpdateFile(
  889. Context context,
  890. Account account,
  891. OCFile existingFile,
  892. Integer behaviour,
  893. NameCollisionPolicy nameCollisionPolicy
  894. ) {
  895. uploadUpdateFile(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy, true);
  896. }
  897. /**
  898. * Upload and overwrite an already uploaded file
  899. */
  900. public static void uploadUpdateFile(
  901. Context context,
  902. Account account,
  903. OCFile existingFile,
  904. Integer behaviour,
  905. NameCollisionPolicy nameCollisionPolicy,
  906. boolean disableRetries
  907. ) {
  908. uploadUpdateFile(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy, disableRetries);
  909. }
  910. /**
  911. * Upload and overwrite already uploaded files
  912. */
  913. public static void uploadUpdateFile(
  914. Context context,
  915. Account account,
  916. OCFile[] existingFiles,
  917. Integer behaviour,
  918. NameCollisionPolicy nameCollisionPolicy,
  919. boolean disableRetries
  920. ) {
  921. Intent intent = new Intent(context, FileUploader.class);
  922. intent.putExtra(FileUploader.KEY_ACCOUNT, account);
  923. intent.putExtra(FileUploader.KEY_FILE, existingFiles);
  924. intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
  925. intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
  926. intent.putExtra(FileUploader.KEY_DISABLE_RETRIES, disableRetries);
  927. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  928. context.startForegroundService(intent);
  929. } else {
  930. context.startService(intent);
  931. }
  932. }
  933. /**
  934. * Retry a failed {@link OCUpload} identified by {@link OCUpload#getRemotePath()}
  935. */
  936. public static void retryUpload(@NonNull Context context, @NonNull Account account, @NonNull OCUpload upload) {
  937. Intent i = new Intent(context, FileUploader.class);
  938. i.putExtra(FileUploader.KEY_RETRY, true);
  939. i.putExtra(FileUploader.KEY_ACCOUNT, account);
  940. i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload);
  941. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  942. context.startForegroundService(i);
  943. } else {
  944. context.startService(i);
  945. }
  946. }
  947. /**
  948. * Retry a subset of all the stored failed uploads.
  949. *
  950. * @param context Caller {@link Context}
  951. * @param account If not null, only failed uploads to this OC account will be retried; otherwise, uploads of
  952. * all accounts will be retried.
  953. * @param uploadResult If not null, only failed uploads with the result specified will be retried; otherwise, failed
  954. * uploads due to any result will be retried.
  955. */
  956. public static void retryFailedUploads(
  957. @NonNull final Context context,
  958. @Nullable final Account account,
  959. @NonNull final UploadsStorageManager uploadsStorageManager,
  960. @NonNull final ConnectivityService connectivityService,
  961. @NonNull final UserAccountManager accountManager,
  962. @NonNull final PowerManagementService powerManagementService,
  963. @Nullable final UploadResult uploadResult
  964. ) {
  965. OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
  966. if(failedUploads.length == 0)
  967. {
  968. //nothing to do
  969. return;
  970. }
  971. Account currentAccount = null;
  972. boolean resultMatch;
  973. boolean accountMatch;
  974. final Connectivity connectivity = connectivityService.getConnectivity();
  975. final boolean gotNetwork = connectivity.isConnected() && !connectivityService.isInternetWalled();
  976. final boolean gotWifi = connectivity.isWifi();
  977. final BatteryStatus batteryStatus = powerManagementService.getBattery();
  978. final boolean charging = batteryStatus.isCharging() || batteryStatus.isFull();
  979. final boolean isPowerSaving = powerManagementService.isPowerSavingEnabled();
  980. for (OCUpload failedUpload : failedUploads) {
  981. accountMatch = account == null || account.name.equals(failedUpload.getAccountName());
  982. resultMatch = uploadResult == null || uploadResult == failedUpload.getLastResult();
  983. if (accountMatch && resultMatch) {
  984. // 1. extract failed upload owner account in efficient name (expensive query)
  985. if (currentAccount == null || !currentAccount.name.equals(failedUpload.getAccountName())) {
  986. currentAccount = failedUpload.getAccount(accountManager);
  987. }
  988. if (!new File(failedUpload.getLocalPath()).exists()) {
  989. // 2A. for deleted files, mark as permanently failed
  990. if (failedUpload.getLastResult() != UploadResult.FILE_NOT_FOUND) {
  991. failedUpload.setLastResult(UploadResult.FILE_NOT_FOUND);
  992. uploadsStorageManager.updateUpload(failedUpload);
  993. }
  994. } else {
  995. // 2B. for existing local files, try restarting it if possible
  996. if (!isPowerSaving && gotNetwork && canUploadBeRetried(failedUpload, gotWifi, charging)) {
  997. retryUpload(context, currentAccount, failedUpload);
  998. }
  999. }
  1000. }
  1001. }
  1002. }
  1003. private static boolean canUploadBeRetried(OCUpload upload, boolean gotWifi, boolean isCharging) {
  1004. File file = new File(upload.getLocalPath());
  1005. boolean needsWifi = upload.isUseWifiOnly();
  1006. boolean needsCharging = upload.isWhileChargingOnly();
  1007. return file.exists() && (!needsWifi || gotWifi) && (!needsCharging || isCharging);
  1008. }
  1009. public static String getUploadsAddedMessage() {
  1010. return FileUploader.class.getName() + UPLOADS_ADDED_MESSAGE;
  1011. }
  1012. public static String getUploadStartMessage() {
  1013. return FileUploader.class.getName() + UPLOAD_START_MESSAGE;
  1014. }
  1015. public static String getUploadFinishMessage() {
  1016. return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
  1017. }
  1018. /**
  1019. * Binder to let client components to perform operations on the queue of uploads.
  1020. *
  1021. * It provides by itself the available operations.
  1022. */
  1023. public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
  1024. /**
  1025. * Map of listeners that will be reported about progress of uploads from a {@link FileUploaderBinder} instance
  1026. */
  1027. private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<>();
  1028. /**
  1029. * Cancels a pending or current upload of a remote file.
  1030. *
  1031. * @param account ownCloud account where the remote file will be stored.
  1032. * @param file A file in the queue of pending uploads
  1033. */
  1034. public void cancel(Account account, OCFile file) {
  1035. cancel(account.name, file.getRemotePath(), null);
  1036. }
  1037. /**
  1038. * Cancels a pending or current upload that was persisted.
  1039. *
  1040. * @param storedUpload Upload operation persisted
  1041. */
  1042. public void cancel(OCUpload storedUpload) {
  1043. cancel(storedUpload.getAccountName(), storedUpload.getRemotePath(), null);
  1044. }
  1045. /**
  1046. * Cancels a pending or current upload of a remote file.
  1047. *
  1048. * @param accountName Local name of an ownCloud account where the remote file will be stored.
  1049. * @param remotePath Remote target of the upload
  1050. * @param resultCode Setting result code will pause rather than cancel the job
  1051. */
  1052. private void cancel(String accountName, String remotePath, @Nullable ResultCode resultCode) {
  1053. Pair<UploadFileOperation, String> removeResult = mPendingUploads.remove(accountName, remotePath);
  1054. UploadFileOperation upload = removeResult.first;
  1055. if (upload == null && mCurrentUpload != null && mCurrentAccount != null &&
  1056. mCurrentUpload.getRemotePath().startsWith(remotePath) && accountName.equals(mCurrentAccount.name)) {
  1057. upload = mCurrentUpload;
  1058. }
  1059. if (upload != null) {
  1060. upload.cancel(resultCode);
  1061. // need to update now table in mUploadsStorageManager,
  1062. // since the operation will not get to be run by FileUploader#uploadFile
  1063. if (resultCode != null) {
  1064. mUploadsStorageManager.updateDatabaseUploadResult(new RemoteOperationResult(resultCode), upload);
  1065. notifyUploadResult(upload, new RemoteOperationResult(resultCode));
  1066. } else {
  1067. mUploadsStorageManager.removeUpload(accountName, remotePath);
  1068. }
  1069. }
  1070. }
  1071. /**
  1072. * Cancels all the uploads for an account.
  1073. *
  1074. * @param account ownCloud account.
  1075. */
  1076. public void cancel(Account account) {
  1077. Log_OC.d(TAG, "Account= " + account.name);
  1078. if (mCurrentUpload != null) {
  1079. Log_OC.d(TAG, "Current Upload Account= " + mCurrentUpload.getAccount().name);
  1080. if (mCurrentUpload.getAccount().name.equals(account.name)) {
  1081. mCurrentUpload.cancel(ResultCode.CANCELLED);
  1082. }
  1083. }
  1084. // Cancel pending uploads
  1085. cancelUploadsForAccount(account);
  1086. }
  1087. public void clearListeners() {
  1088. mBoundListeners.clear();
  1089. }
  1090. /**
  1091. * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting
  1092. * for it
  1093. *
  1094. * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload.
  1095. *
  1096. * Warning: If remote file exists and target was renamed the original file is being returned here. That is, it
  1097. * seems as if the original file is being updated when actually a new file is being uploaded.
  1098. *
  1099. * @param user user where the remote file will be stored.
  1100. * @param file A file that could be in the queue of pending uploads
  1101. */
  1102. public boolean isUploading(User user, OCFile file) {
  1103. if (user == null || file == null) {
  1104. return false;
  1105. }
  1106. return mPendingUploads.contains(user.getAccountName(), file.getRemotePath());
  1107. }
  1108. public boolean isUploadingNow(OCUpload upload) {
  1109. return upload != null &&
  1110. mCurrentAccount != null &&
  1111. mCurrentUpload != null &&
  1112. upload.getAccountName().equals(mCurrentAccount.name) &&
  1113. upload.getRemotePath().equals(mCurrentUpload.getRemotePath());
  1114. }
  1115. /**
  1116. * Adds a listener interested in the progress of the upload for a concrete file.
  1117. *
  1118. * @param listener Object to notify about progress of transfer.
  1119. * @param user user owning the file of interest.
  1120. * @param file {@link OCFile} of interest for listener.
  1121. */
  1122. public void addDatatransferProgressListener(
  1123. OnDatatransferProgressListener listener,
  1124. User user,
  1125. OCFile file
  1126. ) {
  1127. if (user == null || file == null || listener == null) {
  1128. return;
  1129. }
  1130. String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
  1131. mBoundListeners.put(targetKey, listener);
  1132. }
  1133. /**
  1134. * Adds a listener interested in the progress of the upload for a concrete file.
  1135. *
  1136. * @param listener Object to notify about progress of transfer.
  1137. * @param ocUpload {@link OCUpload} of interest for listener.
  1138. */
  1139. public void addDatatransferProgressListener(
  1140. OnDatatransferProgressListener listener,
  1141. OCUpload ocUpload
  1142. ) {
  1143. if (ocUpload == null || listener == null) {
  1144. return;
  1145. }
  1146. String targetKey = buildRemoteName(ocUpload.getAccountName(), ocUpload.getRemotePath());
  1147. mBoundListeners.put(targetKey, listener);
  1148. }
  1149. /**
  1150. * Removes a listener interested in the progress of the upload for a concrete file.
  1151. *
  1152. * @param listener Object to notify about progress of transfer.
  1153. * @param user user owning the file of interest.
  1154. * @param file {@link OCFile} of interest for listener.
  1155. */
  1156. public void removeDatatransferProgressListener(
  1157. OnDatatransferProgressListener listener,
  1158. User user,
  1159. OCFile file
  1160. ) {
  1161. if (user == null || file == null || listener == null) {
  1162. return;
  1163. }
  1164. String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
  1165. if (mBoundListeners.get(targetKey) == listener) {
  1166. mBoundListeners.remove(targetKey);
  1167. }
  1168. }
  1169. /**
  1170. * Removes a listener interested in the progress of the upload for a concrete file.
  1171. *
  1172. * @param listener Object to notify about progress of transfer.
  1173. * @param ocUpload Stored upload of interest
  1174. */
  1175. public void removeDatatransferProgressListener(
  1176. OnDatatransferProgressListener listener,
  1177. OCUpload ocUpload
  1178. ) {
  1179. if (ocUpload == null || listener == null) {
  1180. return;
  1181. }
  1182. String targetKey = buildRemoteName(ocUpload.getAccountName(), ocUpload.getRemotePath());
  1183. if (mBoundListeners.get(targetKey) == listener) {
  1184. mBoundListeners.remove(targetKey);
  1185. }
  1186. }
  1187. @Override
  1188. public void onTransferProgress(
  1189. long progressRate,
  1190. long totalTransferredSoFar,
  1191. long totalToTransfer,
  1192. String fileName
  1193. ) {
  1194. String key = buildRemoteName(mCurrentUpload.getAccount().name, mCurrentUpload.getFile().getRemotePath());
  1195. OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
  1196. if (boundListener != null) {
  1197. boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
  1198. }
  1199. Context context = MainApp.getAppContext();
  1200. if (context != null) {
  1201. ResultCode cancelReason = null;
  1202. Connectivity connectivity = connectivityService.getConnectivity();
  1203. if (mCurrentUpload.isWifiRequired() && !connectivity.isWifi()) {
  1204. cancelReason = ResultCode.DELAYED_FOR_WIFI;
  1205. } else if (mCurrentUpload.isChargingRequired() && !powerManagementService.getBattery().isCharging()) {
  1206. cancelReason = ResultCode.DELAYED_FOR_CHARGING;
  1207. } else if (!mCurrentUpload.isIgnoringPowerSaveMode() && powerManagementService.isPowerSavingEnabled()) {
  1208. cancelReason = ResultCode.DELAYED_IN_POWER_SAVE_MODE;
  1209. }
  1210. if (cancelReason != null) {
  1211. cancel(
  1212. mCurrentUpload.getAccount().name,
  1213. mCurrentUpload.getFile().getRemotePath(),
  1214. cancelReason
  1215. );
  1216. }
  1217. }
  1218. }
  1219. /**
  1220. * Builds a key for the map of listeners.
  1221. *
  1222. * TODO use method in IndexedForest, or refactor both to a common place add to local database) to better policy
  1223. * (add to local database, then upload)
  1224. *
  1225. * @param accountName Local name of the ownCloud account where the file to upload belongs.
  1226. * @param remotePath Remote path to upload the file to.
  1227. * @return Key
  1228. */
  1229. private String buildRemoteName(String accountName, String remotePath) {
  1230. return accountName + remotePath;
  1231. }
  1232. }
  1233. /**
  1234. * Upload worker. Performs the pending uploads in the order they were requested.
  1235. *
  1236. * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
  1237. */
  1238. private static class ServiceHandler extends Handler {
  1239. // don't make it a final class, and don't remove the static ; lint will
  1240. // warn about a possible memory leak
  1241. private FileUploader mService;
  1242. public ServiceHandler(Looper looper, FileUploader service) {
  1243. super(looper);
  1244. if (service == null) {
  1245. throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
  1246. }
  1247. mService = service;
  1248. }
  1249. @Override
  1250. public void handleMessage(Message msg) {
  1251. @SuppressWarnings("unchecked")
  1252. List<String> requestedUploads = (List<String>) msg.obj;
  1253. if (msg.obj != null) {
  1254. for (String requestedUpload : requestedUploads) {
  1255. mService.uploadFile(requestedUpload);
  1256. }
  1257. }
  1258. Log_OC.d(TAG, "Stopping command after id " + msg.arg1);
  1259. mService.mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
  1260. mService.stopForeground(true);
  1261. mService.stopSelf(msg.arg1);
  1262. }
  1263. }
  1264. }