FileUploader.java 54 KB

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