diff --git a/CHANGELOG.md b/CHANGELOG.md index b603ae8bc3b..7d86e562147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 2.9.0 (November 2018) +- Search in current folder +- Select all/inverse files (contribution) +- Improve available offline files synchronization and conflict resolution (Android 5 or higher required) +- Sort files in file picker when uploading (contribution) +- Access ownCloud files from files apps, even with files not downloaded +- New login view +- Show re-shares +- Switch apache and jackrabbit deprecated network libraries to more modern and active library, OkHttp + Dav4Android +- UI improvements, including: + + Change edit share icon + + New gradient in top of the list of files (contribution) + + More accurate message when creating folders with the same name (contribution) +- Bug fixes, including: + + Fix some crashes: + - When rebooting the device + - When copying, moving files or choosing a folder within camera uploads feature + - When creating private/public link + + Fix some failing downloads + + Fix pattern lock being asked very often after disabling fingerprint lock (contribution) + ## 2.9.0 beta v2 (October 2018) - Bug fixes, including: + Fix some crashes: diff --git a/androidTest/java/com/owncloud/android/authentication/AuthenticatorActivityTest.java b/androidTest/java/com/owncloud/android/authentication/AuthenticatorActivityTest.java index c0f9f97dbf5..d218da3620c 100644 --- a/androidTest/java/com/owncloud/android/authentication/AuthenticatorActivityTest.java +++ b/androidTest/java/com/owncloud/android/authentication/AuthenticatorActivityTest.java @@ -270,8 +270,8 @@ public void test3_check_login() //To avoid the short delay when the activity starts SystemClock.sleep(WAIT_INITIAL_MS); - // Check that login button is disabled - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); setFields(testServerURL, testUser, testPassword); @@ -343,8 +343,8 @@ public void test5_check_login_special_characters() Log_OC.i(LOG_TAG, "Test Check Login Special Characters Start"); - // Check that login button is disabled - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); setFields(testServerURL, testUser2, testPassword2); @@ -373,8 +373,8 @@ public void test6_check_login_incorrect() Log_OC.i(LOG_TAG, "Test Check Login Incorrect Start"); - // Check that login button is disabled - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); setFields(testServerURL, USER_INEXISTENT, testPassword); @@ -401,8 +401,8 @@ public void test7_check_existing_account() //Add an account to the device AccountsManager.addAccount(targetContext, testServerURL, testUser, testPassword); - // Check that login button is disabled - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); setFields(testServerURL, testUser, testPassword); @@ -423,8 +423,8 @@ public void test8_check_login_blanks() Log_OC.i(LOG_TAG, "Test Check Blanks Login Start"); - // Check that login button is disabled - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); setFields(testServerURL, "", ""); @@ -446,8 +446,8 @@ public void test9_check_login_trimmed_blanks() String UserBlanks = " " + testUser + " "; - // Check that login button is disabled - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); setFields(testServerURL, UserBlanks, testPassword); @@ -477,8 +477,8 @@ public void test_10_check_url_from_browser() String connectionString = testServerURL + SUFFIX_BROWSER; - // Check that login button is disabled - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); setFields(connectionString, testUser2, testPassword2); @@ -526,7 +526,7 @@ private void setFields (String connectionString, String username, String passwor // Type server url onView(withId(R.id.hostUrlInput)) .perform(replaceText(connectionString), closeSoftKeyboard()); - onView(withId(R.id.scroll)).perform(click()); + onView(withId(R.id.embeddedCheckServerButton)).perform(click()); SystemClock.sleep(WAIT_CONNECTION_MS); checkStatusMessage(); diff --git a/androidTest/java/com/owncloud/android/authentication/SAMLAuthenticatorActivityTest.java b/androidTest/java/com/owncloud/android/authentication/SAMLAuthenticatorActivityTest.java index efaed93541b..11df38f0f10 100644 --- a/androidTest/java/com/owncloud/android/authentication/SAMLAuthenticatorActivityTest.java +++ b/androidTest/java/com/owncloud/android/authentication/SAMLAuthenticatorActivityTest.java @@ -55,6 +55,7 @@ import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard; import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.isEnabled; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; @@ -185,8 +186,8 @@ public void test1_check_login_saml() SystemClock.sleep(WAIT_INITIAL_MS); - // Check that login button is disabled - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); onView(withId(R.id.hostUrlInput)).perform(replaceText(testServerURL)); @@ -236,7 +237,8 @@ public void test2_check_login_saml_orientation_changes() Log_OC.i(LOG_TAG, "Test Check Login SAML Orientation Changes Start"); - onView(withId(R.id.loginButton)).check(matches(not(isEnabled()))); + // Check that login button is hidden + onView(withId(R.id.loginButton)).check(matches(not(isDisplayed()))); onView(withId(R.id.hostUrlInput)).perform(replaceText(testServerURL)); diff --git a/androidTest/java/com/owncloud/android/ui/activity/PublicShareActivityTest.java b/androidTest/java/com/owncloud/android/ui/activity/PublicShareActivityTest.java index 67670c19181..20c70da5a71 100644 --- a/androidTest/java/com/owncloud/android/ui/activity/PublicShareActivityTest.java +++ b/androidTest/java/com/owncloud/android/ui/activity/PublicShareActivityTest.java @@ -688,7 +688,7 @@ public void test_13_capability_allow_public_links() SystemClock.sleep(WAIT_CONNECTION_MS); - AccountsManager.saveCapabilities(capabilities, testServerURL, testUser); + AccountsManager.saveCapabilities(targetContext ,capabilities, testServerURL, testUser); //Select share option selectShare(folder2); @@ -716,7 +716,7 @@ public void test_14_capability_allow_public_uploads() SystemClock.sleep(WAIT_CONNECTION_MS); - AccountsManager.saveCapabilities(capabilities, testServerURL, testUser); + AccountsManager.saveCapabilities(targetContext, capabilities, testServerURL, testUser); //Select share option selectShare(folder2); diff --git a/androidTest/java/com/owncloud/android/utils/AccountsManager.java b/androidTest/java/com/owncloud/android/utils/AccountsManager.java index 91d333cd7b4..6512ed47c27 100644 --- a/androidTest/java/com/owncloud/android/utils/AccountsManager.java +++ b/androidTest/java/com/owncloud/android/utils/AccountsManager.java @@ -115,19 +115,14 @@ public static String regularURL(String url){ //Get server capabilities public static OCCapability getCapabilities (String server, String user, String pass) { - GetRemoteCapabilitiesOperation getCapabilities = new GetRemoteCapabilitiesOperation(); - OwnCloudClient client = new OwnCloudClient(Uri.parse(server), - NetworkUtils.getMultiThreadedConnManager()); - client.setCredentials( - OwnCloudCredentialsFactory.newBasicCredentials(user, pass)); - RemoteOperationResult result = getCapabilities.execute(client); - return (OCCapability) result.getData().get(0); + //REDO -> need mocks or integration with new networking stuff + return new OCCapability(); } //Save capabilities (in device DB) - public static void saveCapabilities (OCCapability capabilities, String server, String user){ - FileDataStorageManager fm = new FileDataStorageManager(new Account(buildAccountName(user, server), accountType), + public static void saveCapabilities (Context context, OCCapability capabilities, String server, String user){ + FileDataStorageManager fm = new FileDataStorageManager(context, new Account(buildAccountName(user, server), accountType), MainApp.getAppContext().getContentResolver()); fm.saveCapabilities (capabilities); } diff --git a/oc_jb_workaround/build.gradle b/oc_jb_workaround/build.gradle index 98b602dd800..6a792e5ad8a 100644 --- a/oc_jb_workaround/build.gradle +++ b/oc_jb_workaround/build.gradle @@ -25,8 +25,8 @@ android { compileSdkVersion 26 defaultConfig { - versionCode = 100039 - versionName = "1.0.39" + versionCode = 100040 + versionName = "1.0.40" } sourceSets { diff --git a/owncloud-android-library b/owncloud-android-library index e0a6c68c667..7a50007ba30 160000 --- a/owncloud-android-library +++ b/owncloud-android-library @@ -1 +1 @@ -Subproject commit e0a6c68c6673494b68c0d6dc4cbd5f4207c3a36b +Subproject commit 7a50007ba303c1e23bdfd1a12b40865ca78b65bb diff --git a/res/layout/files_folder_picker.xml b/res/layout/files_folder_picker.xml index 9c1ea1b2f32..c5aed059a38 100644 --- a/res/layout/files_folder_picker.xml +++ b/res/layout/files_folder_picker.xml @@ -40,7 +40,7 @@ along with this program. If not, see . android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - android:text="@string/common_cancel" + android:text="@android:string/cancel" android:theme="@style/Button.Secondary" /> { + Log_OC.d(TAG, "Requesting retry of camera uploads (& friends)"); + TransferRequester requester = new TransferRequester(); + //Avoid duplicate uploads, because uploads retry is also managed in FileUploader + //by using jobs in versions 5 or higher + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { requester.retryFailedUploads( - MainApp.getAppContext(), - null, - UploadResult.DELAYED_FOR_WIFI // for the rest of enqueued when Wifi fell + MainApp.getAppContext(), + null, + // for the interrupted when Wifi fell, if any + // (side effect: any upload failed due to network error will be + // retried too, instant or not) + UploadResult.NETWORK_CONNECTION, + true ); } + + requester.retryFailedUploads( + MainApp.getAppContext(), + null, + UploadResult.DELAYED_FOR_WIFI, // for the rest of enqueued when Wifi fell + true + ); }, 500 ); diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index 34afb7f2730..5cdb41debe9 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -79,6 +79,7 @@ public class FileDownloader extends Service public static final String KEY_ACCOUNT = "ACCOUNT"; public static final String KEY_FILE = "FILE"; public static final String KEY_IS_AVAILABLE_OFFLINE_FILE = "KEY_IS_AVAILABLE_OFFLINE_FILE"; + public static final String KEY_RETRY_DOWNLOAD = "KEY_RETRY_DOWNLOAD"; private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED"; private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH"; @@ -186,12 +187,13 @@ public int onStartCommand(Intent intent, int flags, int startId) { Log_OC.d(TAG, "Starting command with id " + startId); boolean isAvailableOfflineFile = intent.getBooleanExtra(KEY_IS_AVAILABLE_OFFLINE_FILE, false); + boolean retryDownload = intent.getBooleanExtra(KEY_RETRY_DOWNLOAD, false); - if (isAvailableOfflineFile && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (isAvailableOfflineFile || retryDownload)) { /** - * After calling startForegroundService method from - * {@link com.owncloud.android.operations.SynchronizeFileOperation}, we have to call this within - * five seconds after the service is created to avoid an error + * We have to call this within five seconds after the service is created with startForegroundService when: + * - Checking available offline files in background + * - Retry downloads in background, e.g. when recovering wifi connection */ Log_OC.d(TAG, "Starting FileDownloader service in foreground"); startForeground(1, mNotificationBuilder.build()); @@ -421,6 +423,7 @@ public void handleMessage(Message msg) { } } Log_OC.d(TAG, "Stopping after command with id " + msg.arg1); + mService.stopForeground(true); mService.stopSelf(msg.arg1); } } diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index 208a36388c2..4a1b1556809 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -115,16 +115,12 @@ public class FileUploader extends Service protected static final String KEY_REMOTE_FILE = "REMOTE_FILE"; protected static final String KEY_MIME_TYPE = "MIME_TYPE"; protected static final String KEY_IS_AVAILABLE_OFFLINE_FILE = "KEY_IS_AVAILABLE_OFFLINE_FILE"; + protected static final String KEY_REQUESTED_FROM_WIFI_BACK_EVENT = "KEY_REQUESTED_FROM_WIFI_BACK_EVENT"; /** * Call this Service with only this Intent key if all pending uploads are to be retried. */ protected static final String KEY_RETRY = "KEY_RETRY"; -// /** -// * Call this Service with KEY_RETRY and KEY_RETRY_REMOTE_PATH to retry -// * upload of file identified by KEY_RETRY_REMOTE_PATH. -// */ -// private static final String KEY_RETRY_REMOTE_PATH = "KEY_RETRY_REMOTE_PATH"; /** * Call this Service with KEY_RETRY and KEY_RETRY_UPLOAD to retry * upload of file identified by KEY_RETRY_UPLOAD. @@ -294,9 +290,13 @@ public int onStartCommand(Intent intent, int flags, int startId) { int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER); boolean isAvailableOfflineFile = intent.getBooleanExtra(KEY_IS_AVAILABLE_OFFLINE_FILE, false); + boolean isRequestedFromWifiBackEvent = intent.getBooleanExtra( + KEY_REQUESTED_FROM_WIFI_BACK_EVENT, false + ); - if (((createdBy == CREATED_AS_CAMERA_UPLOAD_PICTURE || createdBy == CREATED_AS_CAMERA_UPLOAD_VIDEO) || - isAvailableOfflineFile) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if ((createdBy == CREATED_AS_CAMERA_UPLOAD_PICTURE || createdBy == CREATED_AS_CAMERA_UPLOAD_VIDEO || + isAvailableOfflineFile || isRequestedFromWifiBackEvent) && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { /** * After calling startForegroundService method from {@link TransferRequester} for camera uploads or * available offline, we have to call this within five seconds after the service is created to avoid @@ -307,7 +307,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { } boolean retry = intent.getBooleanExtra(KEY_RETRY, false); - AbstractList requestedUploads = new Vector(); + AbstractList requestedUploads = new Vector<>(); if (!intent.hasExtra(KEY_ACCOUNT)) { Log_OC.e(TAG, "Not enough information provided in intent"); @@ -370,7 +370,8 @@ public int onStartCommand(Intent intent, int flags, int startId) { files[i] = UploadFileOperation.obtainNewOCFileToUpload( remotePaths[i], localPaths[i], - ((mimeTypes != null) ? mimeTypes[i] : null) + ((mimeTypes != null) ? mimeTypes[i] : null), + getApplicationContext() ); if (files[i] == null) { Log_OC.e(TAG, "obtainNewOCFileToUpload() returned null for remotePaths[i]:" + remotePaths[i] @@ -384,10 +385,10 @@ public int onStartCommand(Intent intent, int flags, int startId) { String uploadKey; UploadFileOperation newUploadFileOperation; try { - for (int i = 0; i < files.length; i++) { + for (OCFile ocFile : files) { - OCUpload ocUpload = new OCUpload(files[i], account); - ocUpload.setFileSize(files[i].getFileLength()); + OCUpload ocUpload = new OCUpload(ocFile, account); + ocUpload.setFileSize(ocFile.getFileLength()); ocUpload.setForceOverwrite(forceOverwrite); ocUpload.setCreateRemoteFolder(isCreateRemoteFolder); ocUpload.setCreatedBy(createdBy); @@ -396,13 +397,13 @@ public int onStartCommand(Intent intent, int flags, int startId) { ocUpload.setWhileChargingOnly(isWhileChargingOnly);*/ ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS); - if(chunked && new File(files[i].getStoragePath()).length() > + if(chunked && new File(ocFile.getStoragePath()).length() > ChunkedUploadRemoteFileOperation.CHUNK_SIZE) { ocUpload.setTransferId( - SecurityUtils.stringToMD5Hash(files[i].getRemotePath()) + System.currentTimeMillis()); + SecurityUtils.stringToMD5Hash(ocFile.getRemotePath()) + System.currentTimeMillis()); newUploadFileOperation = new ChunkedUploadFileOperation( account, - files[i], + ocFile, ocUpload, forceOverwrite, localAction, @@ -411,7 +412,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { } else { newUploadFileOperation = new UploadFileOperation( account, - files[i], + ocFile, ocUpload, forceOverwrite, localAction, @@ -430,7 +431,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { Pair putResult = mPendingUploads.putIfAbsent( account.name, - files[i].getRemotePath(), + ocFile.getRemotePath(), newUploadFileOperation ); if (putResult != null) { @@ -1071,6 +1072,7 @@ uploadResult, upload, getResources() mNotificationManager, R.string.uploader_upload_succeeded_ticker, 2000); + } } } diff --git a/src/com/owncloud/android/files/services/RetryDownloadJobService.java b/src/com/owncloud/android/files/services/RetryDownloadJobService.java index a4947ac69cd..52ab82dc2bf 100644 --- a/src/com/owncloud/android/files/services/RetryDownloadJobService.java +++ b/src/com/owncloud/android/files/services/RetryDownloadJobService.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.os.Build; import android.support.annotation.RequiresApi; +import android.support.v4.content.ContextCompat; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.FileDataStorageManager; @@ -66,10 +67,11 @@ public boolean onStartJob(JobParameters jobParameters) { if (ocFile != null) { // Retry download - Intent i = new Intent(this, FileDownloader.class); - i.putExtra(FileDownloader.KEY_ACCOUNT, account); - i.putExtra(FileDownloader.KEY_FILE, ocFile); - this.startService(i); + Intent intent = new Intent(this, FileDownloader.class); + intent.putExtra(FileDownloader.KEY_ACCOUNT, account); + intent.putExtra(FileDownloader.KEY_FILE, ocFile); + intent.putExtra(FileDownloader.KEY_RETRY_DOWNLOAD, true); + ContextCompat.startForegroundService(this, intent); } else { Log_OC.w( TAG, diff --git a/src/com/owncloud/android/files/services/RetryUploadJobService.java b/src/com/owncloud/android/files/services/RetryUploadJobService.java index b406780fe5b..a803551abb3 100644 --- a/src/com/owncloud/android/files/services/RetryUploadJobService.java +++ b/src/com/owncloud/android/files/services/RetryUploadJobService.java @@ -54,7 +54,7 @@ public boolean onStartJob(JobParameters jobParameters) { if (ocUpload != null) { // Retry the upload TransferRequester requester = new TransferRequester(); - requester.retry(this, ocUpload); + requester.retry(this, ocUpload, true); } else { // easy if the user deletes the upload in uploads view before recovering network diff --git a/src/com/owncloud/android/files/services/TransferRequester.java b/src/com/owncloud/android/files/services/TransferRequester.java index 2c1b02ea20b..1c275710a0d 100644 --- a/src/com/owncloud/android/files/services/TransferRequester.java +++ b/src/com/owncloud/android/files/services/TransferRequester.java @@ -119,7 +119,7 @@ public void uploadNewFile(Context context, Account account, String localPath, St * Call to update multiple files already uploaded */ private void uploadsUpdate(Context context, Account account, OCFile[] existingFiles, Integer behaviour, - Boolean forceOverwrite, boolean requestedFromAvOfflineService) { + Boolean forceOverwrite, boolean requestedFromAvOfflineJobService) { Intent intent = new Intent(context, FileUploader.class); intent.putExtra(FileUploader.KEY_ACCOUNT, account); @@ -129,7 +129,7 @@ private void uploadsUpdate(Context context, Account account, OCFile[] existingFi // Since in Android O and above the apps in background are not allowed to start background // services and available offline feature may try to do it, this is the way to proceed - if (requestedFromAvOfflineService && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && requestedFromAvOfflineJobService) { intent.putExtra(FileUploader.KEY_IS_AVAILABLE_OFFLINE_FILE, true); context.startForegroundService(intent); } else { @@ -141,23 +141,23 @@ private void uploadsUpdate(Context context, Account account, OCFile[] existingFi * Call to update a dingle file already uploaded */ public void uploadUpdate(Context context, Account account, OCFile existingFile, Integer behaviour, - Boolean forceOverwrite, boolean requestedFromAvOfflineService) { + Boolean forceOverwrite, boolean requestedFromAvOfflineJobService) { uploadsUpdate(context, account, new OCFile[]{existingFile}, behaviour, forceOverwrite, - requestedFromAvOfflineService); + requestedFromAvOfflineJobService); } /** * Call to retry upload identified by remotePath */ - public void retry(Context context, OCUpload upload) { + public void retry(Context context, OCUpload upload, boolean requestedFromWifiBackEvent) { if (upload != null && context != null) { Account account = AccountUtils.getOwnCloudAccountByName( context, upload.getAccountName() ); - retry(context, account, upload); + retry(context, account, upload, requestedFromWifiBackEvent); } else { throw new IllegalArgumentException("Null parameter!"); @@ -173,8 +173,11 @@ public void retry(Context context, OCUpload upload) { * uploads of all accounts will be retried. * @param uploadResult If not null, only failed uploads with the result specified will be retried; * otherwise, failed uploads due to any result will be retried. + * @param requestedFromWifiBackEvent true if the retry was requested because wifi connection was back, + * false otherwise */ - public void retryFailedUploads(Context context, Account account, UploadResult uploadResult) { + public void retryFailedUploads(Context context, Account account, UploadResult uploadResult, + boolean requestedFromWifiBackEvent) { UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(context.getContentResolver()); OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads(); Account currentAccount = null; @@ -187,7 +190,7 @@ public void retryFailedUploads(Context context, Account account, UploadResult up !currentAccount.name.equals(failedUpload.getAccountName())) { currentAccount = failedUpload.getAccount(context); } - retry(context, currentAccount, failedUpload); + retry(context, currentAccount, failedUpload, requestedFromWifiBackEvent); } } } @@ -198,8 +201,10 @@ public void retryFailedUploads(Context context, Account account, UploadResult up * @param context Caller {@link Context} * @param account OC account where the upload will be retried. * @param upload Persisted upload to retry. + * @param requestedFromWifiBackEvent true if the retry was requested because wifi connection was back, + * false otherwise */ - private void retry(Context context, Account account, OCUpload upload) { + private void retry(Context context, Account account, OCUpload upload, boolean requestedFromWifiBackEvent) { if (upload != null) { Intent intent = new Intent(context, FileUploader.class); intent.putExtra(FileUploader.KEY_RETRY, true); @@ -207,9 +212,13 @@ private void retry(Context context, Account account, OCUpload upload) { intent.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (upload.getCreatedBy() == - CREATED_AS_CAMERA_UPLOAD_PICTURE || upload.getCreatedBy() == CREATED_AS_CAMERA_UPLOAD_VIDEO)) { + CREATED_AS_CAMERA_UPLOAD_PICTURE || upload.getCreatedBy() == CREATED_AS_CAMERA_UPLOAD_VIDEO || + requestedFromWifiBackEvent)) { // Since in Android O the apps in background are not allowed to start background // services and camera uploads feature may try to do it, this is the way to proceed + if (requestedFromWifiBackEvent) { + intent.putExtra(FileUploader.KEY_REQUESTED_FROM_WIFI_BACK_EVENT, true); + } context.startForegroundService(intent); } else { context.startService(intent); diff --git a/src/com/owncloud/android/operations/RefreshFolderOperation.java b/src/com/owncloud/android/operations/RefreshFolderOperation.java index bac1588ea1d..f39a8d4d47e 100644 --- a/src/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/src/com/owncloud/android/operations/RefreshFolderOperation.java @@ -88,6 +88,8 @@ public class RefreshFolderOperation extends SyncOperation> private LocalBroadcastManager mLocalBroadcastManager; + private boolean syncVersionAndProfileEnabled = true; + /** * Creates a new instance of {@link RefreshFolderOperation}. * @@ -126,7 +128,7 @@ protected RemoteOperationResult> run(OwnCloudClient client mLocalFolder = getStorageManager().getFileByPath(mLocalFolder.getRemotePath()); // only in root folder: sync server version and user profile - if (OCFile.ROOT_PATH.equals(mLocalFolder.getRemotePath())) { + if (OCFile.ROOT_PATH.equals(mLocalFolder.getRemotePath()) && syncVersionAndProfileEnabled) { OwnCloudVersion serverVersion = syncCapabilitiesAndGetServerVersion(); mIsShareSupported = serverVersion.isSharedSupported(); syncUserProfile(); @@ -183,6 +185,16 @@ private OwnCloudVersion syncCapabilitiesAndGetServerVersion() { return serverVersion; } + /** + * Normally user profile and owncloud version get synchronized if you sync the root directory. + * With this you can override this behaviour and disable it, which is useful for the DocumentsProvider + * + * @param syncVersionAndProfileEnabled disables/enables sync Version/Profile when syncing root DIR + */ + public void syncVersionAndProfileEnabled(boolean syncVersionAndProfileEnabled) { + this.syncVersionAndProfileEnabled = syncVersionAndProfileEnabled; + } + /** * Syncs the Share resources for the files contained in the folder refreshed (children, not deeper descendants). * diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index 90b70dfb694..ac37c7ed928 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -58,7 +58,7 @@ public class SynchronizeFileOperation extends SyncOperation { private Context mContext; private boolean mTransferWasRequested = false; - private boolean mRequestedFromAvOfflineService; + private boolean mRequestedFromAvOfflineJobService; /** * Constructor for "full synchronization mode". @@ -84,7 +84,7 @@ public SynchronizeFileOperation( mAccount = account; mPushOnly = false; mContext = context; - mRequestedFromAvOfflineService = false; + mRequestedFromAvOfflineJobService = false; } @@ -107,7 +107,7 @@ public SynchronizeFileOperation( * trying to upload local changes; upload operation will take care of not overwriting * remote content if there are unnoticed changes on the server. * @param context Android context; needed to start transfers. - * @param requestedFromAvOfflineService When 'true' will perform some specific operations + * @param requestedFromAvOfflineJobService When 'true' will perform some specific operations */ public SynchronizeFileOperation( OCFile localFile, @@ -115,7 +115,7 @@ public SynchronizeFileOperation( Account account, boolean pushOnly, Context context, - boolean requestedFromAvOfflineService + boolean requestedFromAvOfflineJobService ) { mLocalFile = localFile; @@ -134,7 +134,7 @@ public SynchronizeFileOperation( mAccount = account; mPushOnly = pushOnly; mContext = context; - mRequestedFromAvOfflineService = requestedFromAvOfflineService; + mRequestedFromAvOfflineJobService = requestedFromAvOfflineJobService; } @@ -240,7 +240,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { private void requestForUpload(OCFile file) { TransferRequester requester = new TransferRequester(); requester.uploadUpdate(mContext, mAccount, file, FileUploader.LOCAL_BEHAVIOUR_MOVE, true, - mRequestedFromAvOfflineService); + mRequestedFromAvOfflineJobService); mTransferWasRequested = true; } @@ -258,7 +258,7 @@ private void requestForDownload(OCFile file) { // Since in Android O and above the apps in background are not allowed to start background // services and available offline feature may try to do it, this is the way to proceed - if (mRequestedFromAvOfflineService && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (mRequestedFromAvOfflineJobService && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.putExtra(FileDownloader.KEY_IS_AVAILABLE_OFFLINE_FILE, true); mContext.startForegroundService(intent); } else { diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index 4d50bea3cde..4ea403b027e 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -33,9 +33,9 @@ import com.owncloud.android.lib.common.http.HttpConstants; import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.operations.OperationCancelledException; -import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; @@ -69,7 +69,8 @@ public class UploadFileOperation extends SyncOperation { public static final int CREATED_AS_CAMERA_UPLOAD_PICTURE = 1; public static final int CREATED_AS_CAMERA_UPLOAD_VIDEO = 2; - public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) { + public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, + Context context) { // MIME type if (mimeType == null || mimeType.length() <= 0) { @@ -77,6 +78,7 @@ public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath } OCFile newFile = new OCFile(remotePath); + newFile.setStoragePath(localPath); newFile.setLastSyncDateForProperties(0); newFile.setLastSyncDateForData(0); @@ -118,7 +120,7 @@ public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath /** * Local path to file which is to be uploaded (before any possible renaming or moving). */ - private String mOriginalStoragePath = null; + private String mOriginalStoragePath; protected Set mDataTransferListeners = new HashSet(); private OnRenameListener mRenameUploadListener; @@ -152,7 +154,8 @@ public UploadFileOperation(Account account, mFile = obtainNewOCFileToUpload( upload.getRemotePath(), upload.getLocalPath(), - upload.getMimeType() + upload.getMimeType(), + context ); } else { mFile = file; diff --git a/src/com/owncloud/android/providers/DocumentsStorageProvider.java b/src/com/owncloud/android/providers/DocumentsStorageProvider.java index 77b85fe4940..8a2ed918868 100644 --- a/src/com/owncloud/android/providers/DocumentsStorageProvider.java +++ b/src/com/owncloud/android/providers/DocumentsStorageProvider.java @@ -30,15 +30,21 @@ import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.graphics.Point; +import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract; import android.provider.DocumentsProvider; +import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.operations.RefreshFolderOperation; import com.owncloud.android.providers.cursors.FileCursor; import com.owncloud.android.providers.cursors.RootCursor; @@ -51,6 +57,15 @@ @TargetApi(Build.VERSION_CODES.KITKAT) public class DocumentsStorageProvider extends DocumentsProvider { + private static final String TAG = DocumentsStorageProvider.class.toString(); + + /** + * If a directory requires to sync, it will write the id of the directory into this variable. + * After the sync function gets triggered again over the same directory, it will see that a sync got already + * triggered, and does not need to be triggered again. This way a endless loop is prevented. + */ + private long requestedFolderIdForSync = -1; + private FileDataStorageManager mCurrentStorageManager = null; private static Map mRootIdToStorageManager; @@ -80,17 +95,58 @@ public Cursor queryDocument(String documentId, String[] projection) { @Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) { - final long folderId = Long.parseLong(parentDocumentId); updateCurrentStorageManagerIfNeeded(folderId); - final FileCursor result = new FileCursor(projection); + final FileCursor resultCursor = new FileCursor(projection); final OCFile browsedDir = mCurrentStorageManager.getFileById(folderId); + + // Create result cursor before syncing folder again, in order to enable faster loading for (OCFile file : mCurrentStorageManager.getFolderContent(browsedDir)) - result.addFile(file); + resultCursor.addFile(file); + + /** + * This will start syncing the current folder. User will only see this after updating his view with a + * pull down, or by accessing the folder again. + */ + if (requestedFolderIdForSync != folderId) { + // register for sync + syncDirectoryWithServer(parentDocumentId, resultCursor); + requestedFolderIdForSync = folderId; + final Bundle extrasBundle = new Bundle(); + extrasBundle.putBoolean(DocumentsContract.EXTRA_LOADING, true); + resultCursor.setMoreToSync(true); + } else { + requestedFolderIdForSync = -1; + } - return result; + return resultCursor; + } + + private void syncDirectoryWithServer(String parentDocumentId, FileCursor cursor) { + final long folderId = Long.parseLong(parentDocumentId); + final RefreshFolderOperation refreshFolderOperation = new RefreshFolderOperation( + mCurrentStorageManager.getFileById(folderId), + false, + false, + mCurrentStorageManager.getAccount(), + getContext()); + refreshFolderOperation.syncVersionAndProfileEnabled(false); + + final ContentResolver contentResolver = getContext().getContentResolver(); + Log_OC.e(TAG, parentDocumentId); + + final Uri browsedDirIdUri = DocumentsContract.buildChildDocumentsUri( + getContext().getString(R.string.document_provider_authority), + parentDocumentId); + cursor.setNotificationUri(contentResolver, + browsedDirIdUri); + Thread thread = new Thread(() -> { + refreshFolderOperation.execute(mCurrentStorageManager, getContext()); + contentResolver.notifyChange(browsedDirIdUri, null); + }); + thread.start(); } @Override @@ -126,8 +182,10 @@ public boolean onCreate() { } @Override - public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { - long docId = Long.parseLong(documentId); + public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) + throws FileNotFoundException { + + final long docId = Long.parseLong(documentId); updateCurrentStorageManagerIfNeeded(docId); OCFile file = mCurrentStorageManager.getFileById(docId); @@ -154,6 +212,9 @@ public Cursor querySearchDocuments(String rootId, String query, String[] project } private void updateCurrentStorageManagerIfNeeded(long docId) { + if (mRootIdToStorageManager == null) { + initiateStorageMap(); + } if (mCurrentStorageManager == null || (mRootIdToStorageManager.containsKey(docId) && mCurrentStorageManager != mRootIdToStorageManager.get(docId))) { @@ -168,8 +229,7 @@ private void updateCurrentStorageManagerIfNeeded(String rootId) { } private void initiateStorageMap() { - - mRootIdToStorageManager = new HashMap(); + mRootIdToStorageManager = new HashMap<>(); ContentResolver contentResolver = getContext().getContentResolver(); diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index fcd9b1f0edd..95f30d94adb 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -40,7 +40,10 @@ import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.owncloud.android.MainApp; @@ -55,6 +58,7 @@ import com.owncloud.android.utils.FileStorageUtils; import java.io.File; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.HashMap; @@ -1312,7 +1316,20 @@ private void updateDownloadedFiles(SQLiteDatabase db, String newAccountName, } finally { c.close(); } + } + + @Nullable + @Override + public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode, @Nullable CancellationSignal signal) + throws FileNotFoundException { + return super.openFile(uri, mode, signal); + } + + @Nullable + @Override + public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { + return super.openFile(uri, mode); } /** diff --git a/src/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java b/src/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java index b6a3b349a22..4d35b9496a3 100644 --- a/src/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java +++ b/src/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java @@ -172,8 +172,16 @@ private Cursor searchForUsersOrGroups(Uri uri) { GetRemoteShareesOperation searchRequest = new GetRemoteShareesOperation( userQuery, REQUESTED_PAGE, RESULTS_PER_PAGE); RemoteOperationResult> result = searchRequest.execute(account, getContext()); + ArrayList names = new ArrayList<>(); - ArrayList names = result.getData(); + if (result.isSuccess()) { + for (Object o : result.getData()) { + // Get JSonObjects from response + names.add((JSONObject) o); + } + } else { + showErrorMessage(result); + } /// convert the responses from the OC server to the expected format if (names.size() > 0) { diff --git a/src/com/owncloud/android/providers/cursors/FileCursor.java b/src/com/owncloud/android/providers/cursors/FileCursor.java index 25cec454ad8..70c4519e833 100644 --- a/src/com/owncloud/android/providers/cursors/FileCursor.java +++ b/src/com/owncloud/android/providers/cursors/FileCursor.java @@ -24,6 +24,8 @@ import android.annotation.TargetApi; import android.database.MatrixCursor; import android.os.Build; +import android.os.Bundle; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import com.owncloud.android.datamodel.OCFile; @@ -38,6 +40,8 @@ public class FileCursor extends MatrixCursor { Document.COLUMN_FLAGS, Document.COLUMN_LAST_MODIFIED }; + private Bundle mExtras = Bundle.EMPTY; + public FileCursor(String[] projection) { super(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); } @@ -58,4 +62,14 @@ public void addFile(OCFile file) { .add(Document.COLUMN_ICON, iconRes) .add(Document.COLUMN_MIME_TYPE, mimeType); } + + public void setMoreToSync(boolean hasMoreToSync) { + mExtras = new Bundle(); + mExtras.putBoolean(DocumentsContract.EXTRA_LOADING, hasMoreToSync); + } + + @Override + public Bundle getExtras() { + return mExtras; + } } diff --git a/src/com/owncloud/android/ui/activity/ManageSpaceActivity.java b/src/com/owncloud/android/ui/activity/ManageSpaceActivity.java index e1db8fb79cd..a8f50c25841 100644 --- a/src/com/owncloud/android/ui/activity/ManageSpaceActivity.java +++ b/src/com/owncloud/android/ui/activity/ManageSpaceActivity.java @@ -35,6 +35,7 @@ import com.owncloud.android.R; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.utils.FileStorageUtils; import java.io.File; @@ -171,7 +172,7 @@ public boolean clearApplicationData() { for (String s : children) { if (!LIB_FOLDER.equals(s)) { File fileToDelete = new File(appDir, s); - clearResult = clearResult && deleteDir(fileToDelete); + clearResult = clearResult && FileStorageUtils.deleteDir(fileToDelete); Log_OC.d(TAG, "Clear Application Data, File: " + fileToDelete.getName() + " DELETED *****"); } } @@ -181,26 +182,5 @@ public boolean clearApplicationData() { } return clearResult; } - - public boolean deleteDir(File dir) { - if (dir != null && dir.isDirectory()) { - String[] children = dir.list(); - if (children != null) { - for (int i = 0; i < children.length; i++) { - boolean success = deleteDir(new File(dir, children[i])); - if (!success) { - Log_OC.w(TAG, "File NOT deleted " + children[i]); - return false; - } else { - Log_OC.d(TAG, "File deleted " + children[i]); - } - } - } else { - return false; - } - } - - return dir.delete(); - } } } diff --git a/src/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java b/src/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java index 722c6895c6f..1b63cea3e8a 100644 --- a/src/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java +++ b/src/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java @@ -6,6 +6,7 @@ * @author Juan Carlos González Cabrero * @author David A. Velasco * @author Christian Schabesberger + * @author David González Verdugo * Copyright (C) 2012 Bartek Przybylski * Copyright (C) 2018 ownCloud GmbH. *

@@ -32,10 +33,10 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; -import android.content.IntentFilter; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources.NotFoundException; import android.net.Uri; import android.os.Bundle; @@ -77,13 +78,14 @@ import com.owncloud.android.operations.common.SyncOperation; import com.owncloud.android.syncadapter.FileSyncAdapter; import com.owncloud.android.ui.adapter.ReceiveExternalFilesAdapter; +import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; import com.owncloud.android.ui.dialog.CreateFolderDialogFragment; -import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask; import com.owncloud.android.ui.errorhandling.ErrorMessageAdapter; import com.owncloud.android.ui.fragment.TaskRetainerFragment; import com.owncloud.android.ui.helpers.UriUploader; import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.Extras; import com.owncloud.android.utils.FileStorageUtils; import java.io.File; @@ -113,6 +115,7 @@ public class ReceiveExternalFilesActivity extends FileActivity private LocalBroadcastManager mLocalBroadcastManager; private SyncBroadcastReceiver mSyncBroadcastReceiver; + private UploadBroadcastReceiver mUploadBroadcastReceiver; private boolean mSyncInProgress = false; private boolean mAccountSelected; private boolean mAccountSelectionShowing; @@ -160,6 +163,12 @@ protected void onCreate(Bundle savedInstanceState) { mLocalBroadcastManager = LocalBroadcastManager.getInstance(this); mLocalBroadcastManager.registerReceiver(mSyncBroadcastReceiver, syncIntentFilter); + // Listen for upload messages + IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.getUploadFinishMessage()); + uploadIntentFilter.addAction(FileUploader.getUploadStartMessage()); + mUploadBroadcastReceiver = new UploadBroadcastReceiver(); + mLocalBroadcastManager.registerReceiver(mUploadBroadcastReceiver, uploadIntentFilter); + // Init Fragment without UI to retain AsyncTask across configuration changes FragmentManager fm = getSupportFragmentManager(); TaskRetainerFragment taskRetainerFragment = @@ -169,7 +178,6 @@ protected void onCreate(Bundle savedInstanceState) { fm.beginTransaction() .add(taskRetainerFragment, TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT).commit(); } // else, Fragment already created and retained across configuration change - } @Override @@ -485,6 +493,13 @@ private void prepareStreamsToUpload() { if (getIntent().getAction().equals(Intent.ACTION_SEND)) { mStreamsToUpload = new ArrayList<>(); mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM)); + if (mStreamsToUpload.get(0) != null) { + String streamToUpload = mStreamsToUpload.get(0).toString(); + if (streamToUpload.contains("/data") && streamToUpload.contains(getPackageName()) && + !streamToUpload.contains(getCacheDir().getPath())) { + finish(); + } + } } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); } @@ -821,25 +836,22 @@ public void afterTextChanged(Editable editable) { @Override public void onShow(DialogInterface dialog) { Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - String fileName = input.getText().toString(); - String error = null; - if (fileName.length() > MAX_FILENAME_LENGTH) { - error = String.format(getString(R.string.uploader_upload_text_dialog_filename_error_length_max), MAX_FILENAME_LENGTH); - } else if (fileName.length() == 0) { - error = getString(R.string.uploader_upload_text_dialog_filename_error_empty); - } else { - fileName += ".txt"; - Uri fileUri = savePlainTextToFile(fileName); - mStreamsToUpload.clear(); - mStreamsToUpload.add(fileUri); - uploadFiles(); - } - inputLayout.setErrorEnabled(error != null); - inputLayout.setError(error); + button.setOnClickListener(view -> { + String fileName = input.getText().toString(); + String error = null; + if (fileName.length() > MAX_FILENAME_LENGTH) { + error = String.format(getString(R.string.uploader_upload_text_dialog_filename_error_length_max), MAX_FILENAME_LENGTH); + } else if (fileName.length() == 0) { + error = getString(R.string.uploader_upload_text_dialog_filename_error_empty); + } else { + fileName += ".txt"; + Uri fileUri = savePlainTextToFile(fileName); + mStreamsToUpload.clear(); + mStreamsToUpload.add(fileUri); + uploadFiles(); } + inputLayout.setErrorEnabled(error != null); + inputLayout.setError(error); }); } }); @@ -861,6 +873,7 @@ private Uri savePlainTextToFile(String fileName) { outputStream.write(content.getBytes()); outputStream.close(); uri = Uri.fromFile(tmpFile); + } catch (IOException e) { Log_OC.w(TAG, "Failed to create temp file for uploading plain text: " + e.getMessage()); } @@ -889,4 +902,18 @@ private void setFileNameFromIntent(AlertDialog alertDialog, EditText input) { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); } } + + private class UploadBroadcastReceiver extends BroadcastReceiver { + /** + * If the upload is text shared from other apps and was successfully uploaded -> delete cache + */ + @Override + public void onReceive(Context context, Intent intent) { + boolean success = intent.getBooleanExtra(Extras.EXTRA_UPLOAD_RESULT, false); + String localPath = intent.getStringExtra(Extras.EXTRA_OLD_FILE_PATH); + if (success && localPath.contains(getCacheDir().getPath())) { + FileStorageUtils.deleteDir(getCacheDir()); + } + } + } } diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index 41de3582cc2..03b52ba8456 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -200,7 +200,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } case R.id.action_retry_uploads: TransferRequester requester = new TransferRequester(); - requester.retryFailedUploads(this, null, null); + requester.retryFailedUploads(this, null, null, false); break; case R.id.action_clear_failed_uploads: @@ -248,7 +248,8 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { requester.retryFailedUploads( this, account, - UploadResult.CREDENTIAL_ERROR + UploadResult.CREDENTIAL_ERROR, + false ); } } @@ -272,7 +273,7 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe } else { // already updated -> just retry! TransferRequester requester = new TransferRequester(); - requester.retryFailedUploads(this, account, UploadResult.CREDENTIAL_ERROR); + requester.retryFailedUploads(this, account, UploadResult.CREDENTIAL_ERROR, false); } } else { diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index ded8d25e5a4..6032d64ccd6 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -388,7 +388,7 @@ public void onClick(View v) { File file = new File(upload.getLocalPath()); if (file.exists()) { TransferRequester requester = new TransferRequester(); - requester.retry(mParentActivity, upload); + requester.retry(mParentActivity, upload, false); refreshView(); } else { final String message = String.format( diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 5917627be29..8493a387ea4 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -266,6 +266,12 @@ public void onPrepareOptionsMenu(Menu menu) { item.setVisible(false); item.setEnabled(false); } + + item = menu.findItem(R.id.action_search); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } } diff --git a/src/com/owncloud/android/ui/preview/PreviewTextFragment.java b/src/com/owncloud/android/ui/preview/PreviewTextFragment.java index f8508ecf95a..b02b0e82dba 100644 --- a/src/com/owncloud/android/ui/preview/PreviewTextFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewTextFragment.java @@ -351,6 +351,12 @@ public void onPrepareOptionsMenu(Menu menu) { item.setVisible(false); item.setEnabled(false); } + + item = menu.findItem(R.id.action_search); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } } /** diff --git a/src/com/owncloud/android/ui/preview/PreviewVideoFragment.java b/src/com/owncloud/android/ui/preview/PreviewVideoFragment.java index dda5516ead3..3781a3c3856 100644 --- a/src/com/owncloud/android/ui/preview/PreviewVideoFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewVideoFragment.java @@ -374,6 +374,12 @@ public void onPrepareOptionsMenu(Menu menu) { item.setVisible(false); item.setEnabled(false); } + + item = menu.findItem(R.id.action_search); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } } /** diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index a8ad0db0acc..5a8c029612f 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -35,6 +35,7 @@ import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.RemoteFile; import java.io.File; @@ -50,6 +51,8 @@ * Static methods to help in access to local file system. */ public class FileStorageUtils { + private static final String TAG = FileStorageUtils.class.getSimpleName(); + public static final int SORT_NAME = 0; public static final int SORT_DATE = 1; public static final int SORT_SIZE = 2; @@ -348,4 +351,24 @@ public static String getMimeTypeFromName(String path) { return (result != null) ? result : ""; } + public static boolean deleteDir(File dir) { + if (dir != null && dir.isDirectory()) { + String[] children = dir.list(); + if (children != null) { + for (int i = 0; i < children.length; i++) { + boolean success = deleteDir(new File(dir, children[i])); + if (!success) { + Log_OC.w(TAG, "File NOT deleted " + children[i]); + return false; + } else { + Log_OC.d(TAG, "File deleted " + children[i]); + } + } + } else { + return false; + } + } + + return dir.delete(); + } }