diff --git a/build.gradle b/build.gradle index 772940d538ee..46b733efcf07 100644 --- a/build.gradle +++ b/build.gradle @@ -11,9 +11,10 @@ buildscript { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:3.0.0-alpha9' classpath 'com.google.gms:google-services:3.0.0' } } @@ -29,7 +30,7 @@ configurations.all { } ext { - supportLibraryVersion = '25.0.0' + supportLibraryVersion = '25.2.0' googleLibraryVersion = '10.2.4' travisBuild = System.getenv("TRAVIS") == "true" @@ -44,6 +45,7 @@ repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + google() flatDir { dirs 'libs' @@ -63,7 +65,7 @@ android { } compileSdkVersion 25 - buildToolsVersion '25.0.0' + buildToolsVersion '25.0.2' defaultConfig { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -78,14 +80,18 @@ android { // adapt structure from Eclipse to Gradle/Android Studio expectations; // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure + flavorDimensions "default" + productFlavors { // used for f-droid generic { applicationId 'com.nextcloud.client' + dimension "default" } gplay { applicationId 'com.nextcloud.client' + dimension "default" } modified { @@ -94,6 +100,7 @@ android { // domain name // .client applicationId 'com.custom.client' + dimension "default" } } @@ -111,11 +118,6 @@ android { preDexLibraries = preDexEnabled && !travisBuild } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 - } - packagingOptions { exclude 'META-INF/LICENSE.txt' exclude 'META-INF/LICENSE' @@ -143,10 +145,10 @@ android { xml.enabled = false html.enabled = true xml { - destination "$project.buildDir/reports/pmd/pmd.xml" + destination = file("$project.buildDir/reports/pmd/pmd.xml") } html { - destination "$project.buildDir/reports/pmd/pmd.html" + destination = file("$project.buildDir/reports/pmd/pmd.html") } } } @@ -165,67 +167,63 @@ android { xml.enabled = false html.enabled = true html { - destination "$project.buildDir/reports/findbugs/findbugs.html" + destination = file("$project.buildDir/reports/findbugs/findbugs.html") } } classpath = files() } check.dependsOn 'checkstyle', 'findbugs', 'pmd', 'lint' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { /// dependencies for app building - compile name: 'touch-image-view' - compile 'com.android.support:multidex:1.0.1' - - compile 'com.github.nextcloud:android-library:1.0.22' - compile "com.android.support:support-v4:${supportLibraryVersion}" - compile "com.android.support:design:${supportLibraryVersion}" - compile 'com.jakewharton:disklrucache:2.0.2' - compile "com.android.support:appcompat-v7:${supportLibraryVersion}" - compile "com.android.support:cardview-v7:${supportLibraryVersion}" - compile 'com.github.tobiasKaminsky:android-floating-action-button:1.10.2' - compile 'com.google.code.findbugs:annotations:2.0.1' - compile group: 'commons-io', name: 'commons-io', version: '2.4' - compile 'com.github.evernote:android-job:v1.1.9' - compile 'com.jakewharton:butterknife:8.4.0' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' - compile 'org.greenrobot:eventbus:3.0.0' - compile 'com.googlecode.ez-vcard:ez-vcard:0.10.2' - + implementation name: 'touch-image-view' + implementation 'com.android.support:multidex:1.0.2' + implementation 'com.github.nextcloud:android-library:1.0.23' + implementation "com.android.support:support-v4:${supportLibraryVersion}" + implementation "com.android.support:design:${supportLibraryVersion}" + implementation 'com.jakewharton:disklrucache:2.0.2' + implementation "com.android.support:appcompat-v7:${supportLibraryVersion}" + implementation "com.android.support:cardview-v7:${supportLibraryVersion}" + implementation "com.android.support:exifinterface:${supportLibraryVersion}" + implementation 'com.github.tobiasKaminsky:android-floating-action-button:1.10.2' + implementation 'com.google.code.findbugs:annotations:2.0.1' + implementation 'commons-io:commons-io:2.5' + implementation 'com.github.evernote:android-job:v1.1.11' + implementation 'com.jakewharton:butterknife:8.5.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' + implementation 'org.greenrobot:eventbus:3.0.0' + implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.2' + implementation 'org.lukhnos:nnio:0.2' // uncomment for gplay, modified - // compile "com.google.firebase:firebase-messaging:${googleLibraryVersion}" - // compile "com.google.android.gms:play-services-base:${googleLibraryVersion}" - - compile 'org.parceler:parceler-api:1.1.6' + // implementation "com.google.firebase:firebase-messaging:${googleLibraryVersion}" + // implementation "com.google.android.gms:play-services-base:${googleLibraryVersion}" + implementation 'org.parceler:parceler-api:1.1.6' annotationProcessor 'org.parceler:parceler:1.1.6' - - compile 'com.github.bumptech.glide:glide:3.7.0' - compile 'com.caverock:androidsvg:1.2.1' - + implementation 'com.github.bumptech.glide:glide:3.7.0' + implementation 'com.caverock:androidsvg:1.2.1' /// dependencies for local unit tests - testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:1.10.19' - + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:1.10.19' /// dependencies for instrumented tests // JUnit4 Rules - androidTestCompile 'com.android.support.test:rules:0.5' - + androidTestImplementation 'com.android.support.test:rules:0.5' // Android JUnit Runner - androidTestCompile 'com.android.support.test:runner:0.5' - + androidTestImplementation 'com.android.support.test:runner:0.5' // Android Annotation Support - androidTestCompile "com.android.support:support-annotations:${supportLibraryVersion}" - + androidTestImplementation "com.android.support:support-annotations:${supportLibraryVersion}" // Espresso core - androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' - + androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2' // UIAutomator - for cross-app UI tests, and to grant screen is turned on in Espresso tests - //androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2' + //androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2' // fix conflict in dependencies; see http://g.co/androidstudio/app-test-app-conflict for details - //androidTestCompile "com.android.support:support-annotations:${supportLibraryVersion}" - + //androidTestImplementation "com.android.support:support-annotations:${supportLibraryVersion}" + implementation 'org.jetbrains:annotations:15.0' } configurations.all { diff --git a/drawable_resources/ic_folder_star.svg b/drawable_resources/ic_folder_star.svg new file mode 100644 index 000000000000..1f6ffa62b43c --- /dev/null +++ b/drawable_resources/ic_folder_star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000000..e8b10e2b5cbb --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +android.enableAapt2=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41b4d0e9c9cb..45b63bc7e56f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 15 00:10:20 CET 2017 +#Fri May 05 19:09:31 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/lint.xml b/lint.xml index 95a6d69bb20d..fc8ace53e278 100644 --- a/lint.xml +++ b/lint.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/scripts/lint/lint-results.txt b/scripts/lint/lint-results.txt index 5416617bda2e..73c612f6e1e3 100644 --- a/scripts/lint/lint-results.txt +++ b/scripts/lint/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 10 errors and 676 warnings \ No newline at end of file + Lint Report: 6 errors and 653 warnings diff --git a/src/gplay/java/com/owncloud/android/utils/PushUtils.java b/src/gplay/java/com/owncloud/android/utils/PushUtils.java index c0fec0eac56c..908a8be7ed4e 100644 --- a/src/gplay/java/com/owncloud/android/utils/PushUtils.java +++ b/src/gplay/java/com/owncloud/android/utils/PushUtils.java @@ -167,7 +167,7 @@ private static void deleteRegistrationForAccount(Account account) { remoteOperationResult = unregisterAccountDeviceForProxyOperation.execute(mClient); if (remoteOperationResult.isSuccess()) { - arbitraryDataProvider.deleteKeyForAccount(account, KEY_PUSH); + arbitraryDataProvider.deleteKeyForAccount(account.name, KEY_PUSH); } } } @@ -244,7 +244,7 @@ public static void pushRegistrationToServer() { PushConfigurationState pushArbitraryData = new PushConfigurationState(token, pushResponse.getDeviceIdentifier(), pushResponse.getSignature(), pushResponse.getPublicKey(), false); - arbitraryDataProvider.storeOrUpdateKeyValue(account, KEY_PUSH, + arbitraryDataProvider.storeOrUpdateKeyValue(account.name, KEY_PUSH, gson.toJson(pushArbitraryData)); } } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 03e534ae3cfd..8ad19e420d04 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -86,7 +86,7 @@ - + @@ -120,6 +120,11 @@ android:label="@string/app_name" android:theme="@style/Theme.ownCloud.Fullscreen" /> + + + @@ -131,7 +136,7 @@ android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> - + @@ -160,7 +165,7 @@ android:label="@string/search_users_and_groups_hint" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java b/src/main/java/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java index 73965abb8ece..0ade8af40714 100644 --- a/src/main/java/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java +++ b/src/main/java/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java @@ -35,7 +35,6 @@ public abstract class SectionedRecyclerViewAdapter mHeaderLocationMap; private GridLayoutManager mLayoutManager; - private ArrayMap mSpanMap; private boolean mShowHeadersForEmptySections; public SectionedRecyclerViewAdapter() { diff --git a/src/main/java/com/owncloud/android/MainApp.java b/src/main/java/com/owncloud/android/MainApp.java index 21883a144584..a7a3f055815f 100644 --- a/src/main/java/com/owncloud/android/MainApp.java +++ b/src/main/java/com/owncloud/android/MainApp.java @@ -19,34 +19,42 @@ */ package com.owncloud.android; +import android.Manifest; import android.app.Activity; -import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.os.Environment; -import android.os.IBinder; import android.support.multidex.MultiDexApplication; import android.support.v4.util.Pair; +import android.support.v7.app.AlertDialog; import com.evernote.android.job.JobManager; import com.owncloud.android.authentication.PassCodeManager; +import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.MediaFolderType; +import com.owncloud.android.datamodel.MediaProvider; import com.owncloud.android.datamodel.SyncedFolder; import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.db.PreferenceManager; +import com.owncloud.android.jobs.NCJobCreator; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.services.NCJobCreator; -import com.owncloud.android.services.observer.SyncedFolderObserverService; import com.owncloud.android.ui.activity.Preferences; +import com.owncloud.android.ui.activity.SyncedFoldersActivity; import com.owncloud.android.ui.activity.WhatsNewActivity; import com.owncloud.android.utils.AnalyticsUtils; +import com.owncloud.android.utils.FilesSyncHelper; +import com.owncloud.android.utils.PermissionUtil; +import com.owncloud.android.utils.ReceiversHelper; import java.util.ArrayList; import java.util.HashMap; @@ -79,8 +87,6 @@ public class MainApp extends MultiDexApplication { private static boolean mOnlyOnDevice = false; - private static SyncedFolderObserverService mObserverService; - @SuppressWarnings("unused") private boolean mBound; @@ -118,14 +124,26 @@ public void onCreate() { Log_OC.d("Debug", "start logging"); } + updateToAutoUpload(); cleanOldEntries(); updateAutoUploadEntries(); - Log_OC.d("SyncedFolderObserverService", "Start service SyncedFolderObserverService"); - Intent i = new Intent(this, SyncedFolderObserverService.class); - startService(i); - bindService(i, syncedFolderObserverServiceConnection, Context.BIND_AUTO_CREATE); + if (PermissionUtil.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + splitOutAutoUploadEntries(); + } else { + PreferenceManager.setAutoUploadSplitEntries(this, true); + } + + initiateExistingAutoUploadEntries(); + + FilesSyncHelper.scheduleFilesSyncIfNeeded(); + FilesSyncHelper.restartJobsIfNeeded(); + ReceiversHelper.registerNetworkChangeReceiver(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + ReceiversHelper.registerPowerChangeReceiver(); + } // register global protection with pass code registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @@ -245,10 +263,6 @@ public static boolean isOnlyOnDevice() { return mOnlyOnDevice; } - public static SyncedFolderObserverService getSyncedFolderObserverService() { - return mObserverService; - } - // user agent public static String getUserAgent() { String appString = getAppContext().getResources().getString(R.string.user_agent); @@ -271,6 +285,48 @@ public static String getUserAgent() { return userAgent; } + private void updateToAutoUpload() { + if (PreferenceManager.instantPictureUploadEnabled(this) || + PreferenceManager.instantPictureUploadEnabled(this)) { + + // remove legacy shared preferences + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); + editor.remove("instant_uploading") + .remove("instant_video_uploading") + .remove("instant_upload_path") + .remove("instant_upload_path_use_subfolders") + .remove("instant_upload_on_wifi") + .remove("instant_upload_on_charging") + .remove("instant_video_upload_path") + .remove("instant_video_upload_path_use_subfolders") + .remove("instant_video_upload_on_wifi") + .remove("instant_video_uploading") + .remove("instant_video_upload_on_charging") + .remove("prefs_instant_behaviour").apply(); + + // show info pop-up + new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog) + .setTitle(R.string.drawer_synced_folders) + .setMessage(R.string.synced_folders_new_info) + .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // show Auto Upload + Intent folderSyncIntent = new Intent(getApplicationContext(), + SyncedFoldersActivity.class); + dialog.dismiss(); + startActivity(folderSyncIntent); + } + }) + .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setIcon(R.drawable.nav_synced_folders) + .show(); + } + } + private void updateAutoUploadEntries() { // updates entries to reflect their true paths if (!PreferenceManager.getAutoUploadPathsUpdate(this)) { @@ -280,6 +336,77 @@ private void updateAutoUploadEntries() { } } + private void splitOutAutoUploadEntries() { + if (!PreferenceManager.getAutoUploadSplitEntries(this)) { + // magic to split out existing synced folders in two when needed + // otherwise, we migrate them to their proper type (image or video) + Log_OC.i(TAG, "Migrate synced_folders records for image/video split"); + ContentResolver contentResolver = this.getContentResolver(); + + SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver); + + final List imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null); + final List videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1); + + ArrayList idsToDelete = new ArrayList<>(); + List syncedFolders = syncedFolderProvider.getSyncedFolders(); + long primaryKey; + SyncedFolder newSyncedFolder; + for (SyncedFolder syncedFolder : syncedFolders) { + idsToDelete.add(syncedFolder.getId()); + Log_OC.i(TAG, "Migration check for synced_folders record: " + + syncedFolder.getId() + " - " + syncedFolder.getLocalPath()); + + for (int i = 0; i < imageMediaFolders.size(); i++) { + if (imageMediaFolders.get(i).absolutePath.equals(syncedFolder.getLocalPath())) { + newSyncedFolder = (SyncedFolder) syncedFolder.clone(); + newSyncedFolder.setType(MediaFolderType.IMAGE); + primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder); + Log_OC.i(TAG, "Migrated image synced_folders record: " + + primaryKey + " - " + newSyncedFolder.getLocalPath()); + break; + } + } + + for (int j = 0; j < videoMediaFolders.size(); j++) { + if (videoMediaFolders.get(j).absolutePath.equals(syncedFolder.getLocalPath())) { + newSyncedFolder = (SyncedFolder) syncedFolder.clone(); + newSyncedFolder.setType(MediaFolderType.VIDEO); + primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder); + Log_OC.i(TAG, "Migrated video synced_folders record: " + + primaryKey + " - " + newSyncedFolder.getLocalPath()); + break; + } + } + } + + for (long id : idsToDelete) { + Log_OC.i(TAG, "Removing legacy synced_folders record: " + id); + syncedFolderProvider.deleteSyncedFolder(id); + } + + PreferenceManager.setAutoUploadSplitEntries(this, true); + } + } + + private void initiateExistingAutoUploadEntries() { + new Thread(() -> { + if (!PreferenceManager.getAutoUploadInit(getAppContext())) { + SyncedFolderProvider syncedFolderProvider = + new SyncedFolderProvider(MainApp.getAppContext().getContentResolver()); + + for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { + if (syncedFolder.isEnabled()) { + FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder); + } + } + + PreferenceManager.setAutoUploadInit(getAppContext(), true); + } + + }).start(); + } + private void cleanOldEntries() { // previous versions of application created broken entries in the SyncedFolderProvider // database, and this cleans all that and leaves 1 (newest) entry per synced folder @@ -302,9 +429,7 @@ private void cleanOldEntries() { } } - for (Long idValue : syncedFolders.values()) { - ids.add(idValue); - } + ids.addAll(syncedFolders.values()); if (ids.size() > 0) { syncedFolderProvider.deleteSyncedFoldersNotInList(mContext, ids); @@ -313,24 +438,4 @@ private void cleanOldEntries() { } } } - - /** - * Defines callbacks for service binding, passed to bindService() - */ - private ServiceConnection syncedFolderObserverServiceConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - SyncedFolderObserverService.SyncedFolderObserverBinder binder = - (SyncedFolderObserverService.SyncedFolderObserverBinder) service; - mObserverService = binder.getService(); - mBound = true; - } - - @Override - public void onServiceDisconnected(ComponentName arg0) { - mBound = false; - } - }; - } diff --git a/src/main/java/com/owncloud/android/authentication/PassCodeManager.java b/src/main/java/com/owncloud/android/authentication/PassCodeManager.java index f78f1e99d204..9ef6fdb2ebcc 100644 --- a/src/main/java/com/owncloud/android/authentication/PassCodeManager.java +++ b/src/main/java/com/owncloud/android/authentication/PassCodeManager.java @@ -31,6 +31,7 @@ import com.owncloud.android.MainApp; import com.owncloud.android.ui.activity.FingerprintActivity; import com.owncloud.android.ui.activity.PassCodeActivity; +import com.owncloud.android.ui.activity.Preferences; import java.util.HashSet; import java.util.Set; @@ -131,6 +132,7 @@ private boolean fingerprintShouldBeRequested() { private boolean fingerprintIsEnabled() { SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(MainApp.getAppContext()); - return (appPrefs.getBoolean(FingerprintActivity.PREFERENCE_USE_FINGERPRINT, false)); + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + appPrefs.getBoolean(Preferences.PREFERENCE_USE_FINGERPRINT, false); } } diff --git a/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java b/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java index b2f54a8f0dfe..3747bce5e02b 100644 --- a/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java +++ b/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java @@ -47,36 +47,43 @@ public ArbitraryDataProvider(ContentResolver contentResolver) { this.contentResolver = contentResolver; } - public int deleteKeyForAccount(Account account, String key) { - int result = contentResolver.delete( + public int deleteKeyForAccount(String account, String key) { + return contentResolver.delete( ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA, ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " = ? AND " + ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + "= ?", - new String[]{account.name, key} + new String[]{account, key} ); + } - return result; + public int deleteForKeyWhereAccountNotIn(ArrayList accounts, String key) { + return contentResolver.delete( + ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA, + ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " NOT IN (?) AND " + + ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + "= ?", + new String[]{String.valueOf(accounts), key} + ); } - public void storeOrUpdateKeyValue(Account account, String key, String newValue) { - ArbitraryDataSet data = getArbitraryDataSet(account, key); + public void storeOrUpdateKeyValue(String accountName, String key, String newValue) { + ArbitraryDataSet data = getArbitraryDataSet(accountName, key); if (data == null) { - Log_OC.v(TAG, "Adding arbitrary data with cloud id: " + account.name + " key: " + key + Log_OC.v(TAG, "Adding arbitrary data with cloud id: " + accountName + " key: " + key + " value: " + newValue); ContentValues cv = new ContentValues(); - cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID, account.name); + cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID, accountName); cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY, key); cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_VALUE, newValue); Uri result = contentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA, cv); if (result == null) { - Log_OC.v(TAG, "Failed to store arbitrary data with cloud id: " + account.name + " key: " + key + Log_OC.v(TAG, "Failed to store arbitrary data with cloud id: " + accountName + " key: " + key + " value: " + newValue); } } else { - Log_OC.v(TAG, "Updating arbitrary data with cloud id: " + account.name + " key: " + key + Log_OC.v(TAG, "Updating arbitrary data with cloud id: " + accountName + " key: " + key + " value: " + newValue); ContentValues cv = new ContentValues(); cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID, data.getCloudId()); @@ -91,7 +98,7 @@ public void storeOrUpdateKeyValue(Account account, String key, String newValue) ); if (result == 0) { - Log_OC.v(TAG, "Failed to update arbitrary data with cloud id: " + account.name + " key: " + key + Log_OC.v(TAG, "Failed to update arbitrary data with cloud id: " + accountName + " key: " + key + " value: " + newValue); } } @@ -146,38 +153,6 @@ public Integer getIntegerValue(Account account, String key) { return getIntegerValue(account.name, key); } - private ArrayList getValues(Account account, String key) { - Cursor cursor = contentResolver.query( - ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA, - null, - ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " = ? and " + - ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + " = ?", - new String[]{account.name, key}, - null - ); - - if (cursor != null) { - ArrayList list = new ArrayList<>(); - if (cursor.moveToFirst()) { - do { - String value = cursor.getString(cursor.getColumnIndex( - ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_VALUE)); - if (value == null) { - Log_OC.e(TAG, "Arbitrary value could not be created from cursor"); - } else { - list.add(value); - } - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } else { - Log_OC.e(TAG, "DB error restoring arbitrary values."); - } - - return new ArrayList<>(); - } - /** * Returns stored value as string or empty string * @return string if value found or empty string @@ -215,13 +190,13 @@ public String getValue(String accountName, String key) { return ""; } - private ArbitraryDataSet getArbitraryDataSet(Account account, String key) { + private ArbitraryDataSet getArbitraryDataSet(String accountName, String key) { Cursor cursor = contentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA, null, ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " = ? and " + ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + " = ?", - new String[]{account.name, key}, + new String[]{accountName, key}, null ); diff --git a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index db93a2d26dcc..deb041db77bc 100644 --- a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -593,7 +593,6 @@ private boolean removeLocalFolder(File localFolder) { if (localFile.isDirectory()) { success &= removeLocalFolder(localFile); } else { - String path = localFile.getAbsolutePath(); success &= localFile.delete(); } } @@ -602,7 +601,6 @@ private boolean removeLocalFolder(File localFolder) { return success; } - /** * Updates database and file system for a file or folder that was moved to a different location. * diff --git a/src/main/java/com/owncloud/android/datamodel/FileSystemDataSet.java b/src/main/java/com/owncloud/android/datamodel/FileSystemDataSet.java new file mode 100644 index 000000000000..59bc0c9c67db --- /dev/null +++ b/src/main/java/com/owncloud/android/datamodel/FileSystemDataSet.java @@ -0,0 +1,105 @@ +/** + * Nextcloud Android client application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * Copyright (C) 2017 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ +package com.owncloud.android.datamodel; + +/* + Model for filesystem data from the database + */ +public class FileSystemDataSet { + + private int id; + private String localPath; + private long modifiedAt; + private boolean isFolder; + private boolean isSentForUpload; + private long foundAt; + private long syncedFolderId; + + public FileSystemDataSet() { + } + + public FileSystemDataSet(int id, String localPath, long modifiedAt, boolean isFolder, + boolean isSentForUpload, long foundAt, long syncedFolderId) { + this.id = id; + this.localPath = localPath; + this.modifiedAt = modifiedAt; + this.isFolder = isFolder; + this.isSentForUpload = isSentForUpload; + this.foundAt = foundAt; + this.syncedFolderId = syncedFolderId; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getLocalPath() { + return localPath; + } + + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + + public long getModifiedAt() { + return modifiedAt; + } + + public void setModifiedAt(long modifiedAt) { + this.modifiedAt = modifiedAt; + } + + public boolean isFolder() { + return isFolder; + } + + public void setFolder(boolean folder) { + isFolder = folder; + } + + public long getFoundAt() { + return foundAt; + } + + public void setFoundAt(long foundAt) { + this.foundAt = foundAt; + } + + public boolean isSentForUpload() { + return isSentForUpload; + } + + public void setSentForUpload(boolean sentForUpload) { + isSentForUpload = sentForUpload; + } + + public long getSyncedFolderId() { + return syncedFolderId; + } + + public void setSyncedFolderId(long syncedFolderId) { + this.syncedFolderId = syncedFolderId; + } +} diff --git a/src/main/java/com/owncloud/android/datamodel/FilesystemDataProvider.java b/src/main/java/com/owncloud/android/datamodel/FilesystemDataProvider.java new file mode 100644 index 000000000000..7b16fe14defa --- /dev/null +++ b/src/main/java/com/owncloud/android/datamodel/FilesystemDataProvider.java @@ -0,0 +1,186 @@ +/** + * Nextcloud Android client application + * + * Copyright (C) 2017 Mario Danic + * Copyright (C) 2017 Nextcloud. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ +package com.owncloud.android.datamodel; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import com.owncloud.android.db.ProviderMeta; +import com.owncloud.android.lib.common.utils.Log_OC; + +import java.util.HashSet; +import java.util.Set; + +/** + * Provider for stored filesystem data. + */ +public class FilesystemDataProvider { + + static private final String TAG = FilesystemDataProvider.class.getSimpleName(); + + private ContentResolver contentResolver; + + public FilesystemDataProvider(ContentResolver contentResolver) { + if (contentResolver == null) { + throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver"); + } + this.contentResolver = contentResolver; + } + + public void updateFilesystemFileAsSentForUpload(String path, String syncedFolderId) { + ContentValues cv = new ContentValues(); + cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD, 1); + + contentResolver.update( + ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM, + cv, + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH + " = ? and " + + ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID + " = ?", + new String[]{path, syncedFolderId} + ); + } + + public Set getFilesForUpload(String localPath, String syncedFolderId) { + Set localPathsToUpload = new HashSet<>(); + + String likeParam = localPath + "%"; + + Cursor cursor = contentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM, + null, + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH + " LIKE ? and " + + ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID + " = ? and " + + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD + " = ? and " + + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER + " = ?", + new String[]{likeParam, syncedFolderId, "0", "0"}, + null); + + if (cursor != null && cursor.moveToFirst()) { + do { + String value = cursor.getString(cursor.getColumnIndex( + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH)); + if (value == null) { + Log_OC.e(TAG, "Cannot get local path"); + } else { + localPathsToUpload.add(value); + } + } while (cursor.moveToNext()); + + cursor.close(); + } + + return localPathsToUpload; + } + + public void storeOrUpdateFileValue(String localPath, long modifiedAt, boolean isFolder, SyncedFolder syncedFolder) { + + FileSystemDataSet data = getFilesystemDataSet(localPath, syncedFolder); + + int isFolderValue = 0; + if (isFolder) { + isFolderValue = 1; + } + + ContentValues cv = new ContentValues(); + cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_FOUND_RECENTLY, System.currentTimeMillis()); + cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_MODIFIED, modifiedAt); + + if (data == null) { + + cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH, localPath); + cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER, isFolderValue); + cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD, false); + cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID, syncedFolder.getId()); + + Uri result = contentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM, cv); + + if (result == null) { + Log_OC.v(TAG, "Failed to insert filesystem data with local path: " + localPath); + } + } else { + + if (data.getModifiedAt() != modifiedAt) { + cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD, 0); + } + + + int result = contentResolver.update( + ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM, + cv, + ProviderMeta.ProviderTableMeta._ID + "=?", + new String[]{String.valueOf(data.getId())} + ); + + if (result == 0) { + Log_OC.v(TAG, "Failed to update filesystem data with local path: " + localPath); + } + } + } + + private FileSystemDataSet getFilesystemDataSet(String localPathParam, SyncedFolder syncedFolder) { + + Cursor cursor = contentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM, + null, + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH + " = ? and " + + ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID + " = ?", + new String[]{localPathParam, Long.toString(syncedFolder.getId())}, + null + ); + + FileSystemDataSet dataSet = null; + if (cursor != null) { + if (cursor.moveToFirst()) { + int id = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta._ID)); + String localPath = cursor.getString(cursor.getColumnIndex( + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH)); + long modifiedAt = cursor.getLong(cursor.getColumnIndex( + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_MODIFIED)); + boolean isFolder = false; + if (cursor.getInt(cursor.getColumnIndex( + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER)) != 0) { + isFolder = true; + } + long foundAt = cursor.getLong(cursor.getColumnIndex(ProviderMeta. + ProviderTableMeta.FILESYSTEM_FILE_FOUND_RECENTLY)); + + boolean isSentForUpload = false; + if (cursor.getInt(cursor.getColumnIndex( + ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD)) != 0) { + isSentForUpload = true; + } + + if (id == -1) { + Log_OC.e(TAG, "Arbitrary value could not be created from cursor"); + } else { + dataSet = new FileSystemDataSet(id, localPath, modifiedAt, isFolder, isSentForUpload, foundAt, + syncedFolder.getId()); + } + } + cursor.close(); + } else { + Log_OC.e(TAG, "DB error restoring arbitrary values."); + } + + return dataSet; + } +} diff --git a/src/main/java/com/owncloud/android/datamodel/MediaFolder.java b/src/main/java/com/owncloud/android/datamodel/MediaFolder.java index e3e29861db4a..b2655e0b557f 100644 --- a/src/main/java/com/owncloud/android/datamodel/MediaFolder.java +++ b/src/main/java/com/owncloud/android/datamodel/MediaFolder.java @@ -4,17 +4,17 @@ * @author Andy Scherzinger * Copyright (C) 2016 Andy Scherzinger * Copyright (C) 2016 Nextcloud - *

+ * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; either * version 3 of the License, or any later version. - *

+ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - *

+ * * You should have received a copy of the GNU Affero General Public * License along with this program. If not, see . */ @@ -38,4 +38,7 @@ public class MediaFolder { /** total number of files in the media folder. */ public long numberOfFiles; + + /** type of media folder. */ + public MediaFolderType type; } diff --git a/src/main/java/com/owncloud/android/datamodel/MediaFolderType.java b/src/main/java/com/owncloud/android/datamodel/MediaFolderType.java new file mode 100644 index 000000000000..2a8b30a57290 --- /dev/null +++ b/src/main/java/com/owncloud/android/datamodel/MediaFolderType.java @@ -0,0 +1,53 @@ +/* + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2017 Andy Scherzinger + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ +package com.owncloud.android.datamodel; + +import android.util.SparseArray; + +/** + * Types of media folder. + */ +public enum MediaFolderType { + CUSTOM(0), + IMAGE(1), + VIDEO(2); + + private Integer id; + + private static SparseArray reverseMap = new SparseArray<>(3); + + static { + reverseMap.put(CUSTOM.getId(), CUSTOM); + reverseMap.put(IMAGE.getId(), IMAGE); + reverseMap.put(VIDEO.getId(), VIDEO); + } + + MediaFolderType(Integer id) { + this.id = id; + } + + public static MediaFolderType getById(Integer id) { + return reverseMap.get(id); + } + + public Integer getId() { + return id; + } +} diff --git a/src/main/java/com/owncloud/android/datamodel/MediaProvider.java b/src/main/java/com/owncloud/android/datamodel/MediaProvider.java index 4bc356dd43c4..4395d8f44493 100644 --- a/src/main/java/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/main/java/com/owncloud/android/datamodel/MediaProvider.java @@ -40,6 +40,8 @@ import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; + /** * Media queries to gain access to media lists for the device. */ @@ -47,12 +49,15 @@ public class MediaProvider { private static final String TAG = MediaProvider.class.getSimpleName(); // fixed query parameters - private static final Uri MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + private static final Uri IMAGES_MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; private static final String[] FILE_PROJECTION = new String[]{MediaStore.MediaColumns.DATA}; - private static final String FILE_SELECTION = MediaStore.Images.Media.BUCKET_ID + "="; - private static final String[] FOLDER_PROJECTION = { "Distinct " + MediaStore.Images.Media.BUCKET_ID, - MediaStore.Images.Media.BUCKET_DISPLAY_NAME }; - private static final String FOLDER_SORT_ORDER = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " ASC"; + private static final String IMAGES_FILE_SELECTION = MediaStore.Images.Media.BUCKET_ID + "="; + private static final String[] IMAGES_FOLDER_PROJECTION = {"Distinct " + MediaStore.Images.Media.BUCKET_ID, + MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; + private static final String IMAGES_FOLDER_SORT_ORDER = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " ASC"; + + private static final String[] VIDEOS_FOLDER_PROJECTION = {"Distinct " + MediaStore.Video.Media.BUCKET_ID, + MediaStore.Video.Media.BUCKET_DISPLAY_NAME}; /** * Getting All Images Paths. @@ -61,11 +66,105 @@ public class MediaProvider { * @param itemLimit the number of media items (usually images) to be returned per media folder. * @return list with media folders */ - public static List getMediaFolders(ContentResolver contentResolver, int itemLimit, - final Activity activity) { + public static List getImageFolders(ContentResolver contentResolver, int itemLimit, + @Nullable final Activity activity) { // check permissions - if (!PermissionUtil.checkSelfPermission(activity.getApplicationContext(), + checkPermissions(activity); + + + // query media/image folders + Cursor cursorFolders; + if (activity != null && PermissionUtil.checkSelfPermission(activity.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + cursorFolders = contentResolver.query( + IMAGES_MEDIA_URI, + IMAGES_FOLDER_PROJECTION, + null, + null, + IMAGES_FOLDER_SORT_ORDER + ); + } else { + cursorFolders = contentResolver.query( + IMAGES_MEDIA_URI, + IMAGES_FOLDER_PROJECTION, + null, + null, + IMAGES_FOLDER_SORT_ORDER + ); + } + List mediaFolders = new ArrayList<>(); + String dataPath = MainApp.getStoragePath() + File.separator + MainApp.getDataFolder(); + + if (cursorFolders != null) { + String folderName; + String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT " + itemLimit; + Cursor cursorImages; + + while (cursorFolders.moveToNext()) { + String folderId = cursorFolders.getString(cursorFolders.getColumnIndex(MediaStore.Images.Media + .BUCKET_ID)); + + MediaFolder mediaFolder = new MediaFolder(); + folderName = cursorFolders.getString(cursorFolders.getColumnIndex( + MediaStore.Images.Media.BUCKET_DISPLAY_NAME)); + mediaFolder.type = MediaFolderType.IMAGE; + mediaFolder.folderName = folderName; + mediaFolder.filePaths = new ArrayList<>(); + + // query images + cursorImages = contentResolver.query( + IMAGES_MEDIA_URI, + FILE_PROJECTION, + IMAGES_FILE_SELECTION + folderId, + null, + fileSortOrder + ); + Log.d(TAG, "Reading images for " + mediaFolder.folderName); + + if (cursorImages != null) { + String filePath; + + while (cursorImages.moveToNext()) { + filePath = cursorImages.getString(cursorImages.getColumnIndexOrThrow( + MediaStore.MediaColumns.DATA)); + + if (filePath != null) { + mediaFolder.filePaths.add(filePath); + mediaFolder.absolutePath = filePath.substring(0, filePath.lastIndexOf("/")); + } + } + cursorImages.close(); + + // only do further work if folder is not within the Nextcloud app itself + if (!mediaFolder.absolutePath.startsWith(dataPath)) { + + // count images + Cursor count = contentResolver.query( + IMAGES_MEDIA_URI, + FILE_PROJECTION, + IMAGES_FILE_SELECTION + folderId, + null, + null); + + if (count != null) { + mediaFolder.numberOfFiles = count.getCount(); + count.close(); + } + + mediaFolders.add(mediaFolder); + } + } + } + cursorFolders.close(); + } + + return mediaFolders; + } + + private static void checkPermissions(@Nullable Activity activity) { + if (activity != null && + !PermissionUtil.checkSelfPermission(activity.getApplicationContext(), + Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Check if we should show an explanation if (PermissionUtil.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { @@ -87,49 +186,46 @@ public void onClick(View v) { PermissionUtil.requestWriteExternalStoreagePermission(activity); } } + } - // query media/image folders - Cursor cursorFolders = null; - if (PermissionUtil.checkSelfPermission(activity.getApplicationContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - cursorFolders = contentResolver.query(MEDIA_URI, FOLDER_PROJECTION, null, null, FOLDER_SORT_ORDER); - } + public static List getVideoFolders(ContentResolver contentResolver, int itemLimit) { + Cursor cursorFolders = contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + VIDEOS_FOLDER_PROJECTION, null, null, null); List mediaFolders = new ArrayList<>(); String dataPath = MainApp.getStoragePath() + File.separator + MainApp.getDataFolder(); if (cursorFolders != null) { String folderName; - String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT " + itemLimit; + String fileSortOrder = MediaStore.Video.Media.DATE_TAKEN + " DESC LIMIT " + itemLimit; Cursor cursorImages; while (cursorFolders.moveToNext()) { - String folderId = cursorFolders.getString(cursorFolders.getColumnIndex(MediaStore.Images.Media + String folderId = cursorFolders.getString(cursorFolders.getColumnIndex(MediaStore.Video.Media .BUCKET_ID)); MediaFolder mediaFolder = new MediaFolder(); folderName = cursorFolders.getString(cursorFolders.getColumnIndex( - MediaStore.Images.Media.BUCKET_DISPLAY_NAME)); + MediaStore.Video.Media.BUCKET_DISPLAY_NAME)); + mediaFolder.type = MediaFolderType.VIDEO; mediaFolder.folderName = folderName; mediaFolder.filePaths = new ArrayList<>(); // query images - cursorImages = contentResolver.query(MEDIA_URI, FILE_PROJECTION, FILE_SELECTION + folderId, null, + cursorImages = contentResolver.query( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + FILE_PROJECTION, + MediaStore.Video.Media.BUCKET_ID + "=" + folderId, + null, fileSortOrder); - Log.d(TAG, "Reading images for " + mediaFolder.folderName); + Log.d(TAG, "Reading videos for " + mediaFolder.folderName); if (cursorImages != null) { String filePath; - int failedImages = 0; while (cursorImages.moveToNext()) { filePath = cursorImages.getString(cursorImages.getColumnIndexOrThrow( MediaStore.MediaColumns.DATA)); - - if (filePath != null) { - mediaFolder.filePaths.add(filePath); - mediaFolder.absolutePath = filePath.substring(0, filePath.lastIndexOf("/")); - } else { - failedImages++; - } + mediaFolder.filePaths.add(filePath); + mediaFolder.absolutePath = filePath.substring(0, filePath.lastIndexOf("/")); } cursorImages.close(); @@ -138,14 +234,14 @@ public void onClick(View v) { // count images Cursor count = contentResolver.query( - MEDIA_URI, + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, FILE_PROJECTION, - FILE_SELECTION + folderId, + MediaStore.Video.Media.BUCKET_ID + "=" + folderId, null, null); if (count != null) { - mediaFolder.numberOfFiles = count.getCount() - failedImages; + mediaFolder.numberOfFiles = count.getCount(); count.close(); } diff --git a/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java b/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java index a491d993a612..4ba9038d7fb4 100644 --- a/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java +++ b/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java @@ -26,7 +26,7 @@ /** * Synced folder entity containing all information per synced folder. */ -public class SyncedFolder implements Serializable { +public class SyncedFolder implements Serializable, Cloneable { public static final long UNPERSISTED_ID = Long.MIN_VALUE; private static final long serialVersionUID = -793476118299906429L; private long id = UNPERSISTED_ID; @@ -38,6 +38,7 @@ public class SyncedFolder implements Serializable { private String account; private Integer uploadAction; private boolean enabled; + private MediaFolderType type; /** * constructor for already persisted entity. @@ -51,9 +52,11 @@ public class SyncedFolder implements Serializable { * @param account the account owning the synced folder * @param uploadAction the action to be done after the upload * @param enabled flag if synced folder config is active + * @param type the type of the folder */ public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, - Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) { + Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, + MediaFolderType type) { this.id = id; this.localPath = localPath; this.remotePath = remotePath; @@ -63,6 +66,7 @@ public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOn this.account = account; this.uploadAction = uploadAction; this.enabled = enabled; + this.type = type; } /** @@ -76,9 +80,11 @@ public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOn * @param account the account owning the synced folder * @param uploadAction the action to be done after the upload * @param enabled flag if synced folder config is active + * @param type the type of the folder */ public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, - Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) { + Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, + MediaFolderType type) { this.localPath = localPath; this.remotePath = remotePath; this.wifiOnly = wifiOnly; @@ -87,6 +93,15 @@ public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boole this.account = account; this.uploadAction = uploadAction; this.enabled = enabled; + this.type = type; + } + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } } public long getId() { @@ -160,4 +175,12 @@ public boolean isEnabled() { public void setEnabled(boolean enabled) { this.enabled = enabled; } + + public MediaFolderType getType() { + return type; + } + + public void setType(MediaFolderType type) { + this.type = type; + } } \ No newline at end of file diff --git a/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java b/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java index 5ee7e90bef4d..f0cfebc21e97 100644 --- a/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java +++ b/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java @@ -47,11 +47,13 @@ public class SyncedFolderDisplayItem extends SyncedFolder { * @param filePaths the UI info for the file path * @param folderName the UI info for the folder's name * @param numberOfFiles the UI info for number of files within the folder + * @param type the type of the folder */ public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, - List filePaths, String folderName, long numberOfFiles) { - super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled); + List filePaths, String folderName, long numberOfFiles, MediaFolderType type) + { + super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled, type); this.filePaths = filePaths; this.folderName = folderName; this.numberOfFiles = numberOfFiles; @@ -59,12 +61,11 @@ public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boo public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, - String folderName) { - super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled); + String folderName, MediaFolderType type) { + super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled, type); this.folderName = folderName; } - public List getFilePaths() { return filePaths; } diff --git a/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java index ee3b1762c708..a9e4d5ea6dc4 100644 --- a/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -1,21 +1,22 @@ /** - * Nextcloud Android client application + * Nextcloud Android client application * - * Copyright (C) 2016 Andy Scherzinger - * Copyright (C) 2016 Nextcloud. + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. * - * You should have received a copy of the GNU Affero General Public - * License along with this program. If not, see . + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . */ package com.owncloud.android.datamodel; @@ -27,7 +28,6 @@ import android.net.Uri; import android.support.annotation.NonNull; -import com.owncloud.android.MainApp; import com.owncloud.android.db.PreferenceManager; import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.lib.common.utils.Log_OC; @@ -58,12 +58,12 @@ public SyncedFolderProvider(ContentResolver contentResolver) { } /** - * Stores an media folder sync object in database. + * Stores a synced folder object in database. * * @param syncedFolder synced folder to store * @return synced folder id, -1 if the insert process fails. */ - public long storeFolderSync(SyncedFolder syncedFolder) { + public long storeSyncedFolder(SyncedFolder syncedFolder) { Log_OC.v(TAG, "Inserting " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); @@ -71,7 +71,6 @@ public long storeFolderSync(SyncedFolder syncedFolder) { Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv); if (result != null) { - notifyFolderSyncObservers(syncedFolder); return Long.parseLong(result.getPathSegments().get(1)); } else { Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db."); @@ -118,12 +117,12 @@ public List getSyncedFolders() { /** * Update upload status of file uniquely referenced by id. * - * @param id folder sync id. + * @param id synced folder id. * @param enabled new status. * @return the number of rows updated. */ - public int updateFolderSyncEnabled(long id, Boolean enabled) { - Log_OC.v(TAG, "Storing sync folder id" + id + " with enabled=" + enabled); + public int updateSyncedFolderEnabled(long id, Boolean enabled) { + Log_OC.v(TAG, "Storing synced folder id" + id + " with enabled=" + enabled); int result = 0; Cursor cursor = mContentResolver.query( @@ -187,7 +186,6 @@ public SyncedFolder findByLocalPath(String localPath) { } return result; - } /** @@ -211,15 +209,12 @@ public int deleteSyncFoldersForAccount(Account account) { * * @param id for the synced folder. */ - private int deleteSyncFolderWithId(long id) { - int result = mContentResolver.delete( + return mContentResolver.delete( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, ProviderMeta.ProviderTableMeta._ID + " = ?", new String[]{String.valueOf(id)} ); - - return result; } @@ -274,6 +269,17 @@ public int deleteSyncedFoldersNotInList(Context context, ArrayList ids) { return result; } + /** + * delete record of synchronized folder with the given id. + */ + public int deleteSyncedFolder(long id) { + return mContentResolver.delete( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + ProviderMeta.ProviderTableMeta._ID + " = ?", + new String[]{String.valueOf(id)} + ); + } + /** * update given synced folder. * @@ -292,10 +298,6 @@ public int updateSyncFolder(SyncedFolder syncedFolder) { new String[]{String.valueOf(syncedFolder.getId())} ); - if (result > 0) { - notifyFolderSyncObservers(syncedFolder); - } - return result; } @@ -325,9 +327,11 @@ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) { ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); Boolean enabled = cursor.getInt(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; + MediaFolderType type = MediaFolderType.getById(cursor.getInt(cursor.getColumnIndex( + ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE))); syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, - accountName, uploadAction, enabled); + accountName, uploadAction, enabled, type); } return syncedFolder; } @@ -349,18 +353,8 @@ private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFol cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction()); - return cv; - } + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().getId()); - /** - * Inform all observers about data change. - * - * @param syncedFolder changed, synchronized folder - */ - private void notifyFolderSyncObservers(SyncedFolder syncedFolder) { - if (syncedFolder != null) { - MainApp.getSyncedFolderObserverService().restartObserver(syncedFolder); - Log_OC.d(TAG, "notifying folder sync data observers for changed/added: " + syncedFolder.getLocalPath()); - } + return cv; } } diff --git a/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index a9ceadc77472..7ed0a3a8bd8c 100644 --- a/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -30,6 +30,7 @@ import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.media.MediaMetadataRetriever; import android.media.ThumbnailUtils; import android.net.Uri; import android.os.AsyncTask; @@ -418,6 +419,7 @@ private Bitmap doFileInBackground() { } public static class MediaThumbnailGenerationTask extends AsyncTask { + private enum Type {IMAGE, VIDEO} private final WeakReference mImageViewReference; private File mFile; private String mImageKey = null; @@ -439,7 +441,9 @@ protected Bitmap doInBackground(Object... params) { } if (MimeTypeUtil.isImage(mFile)) { - thumbnail = doFileInBackground(mFile); + thumbnail = doFileInBackground(mFile, Type.IMAGE); + } else if (MimeTypeUtil.isVideo(mFile)) { + thumbnail = doFileInBackground(mFile, Type.VIDEO); } } } // the app should never break due to a problem with thumbnails @@ -482,7 +486,7 @@ protected void onPostExecute(Bitmap bitmap) { } } - private Bitmap doFileInBackground(File file) { + private Bitmap doFileInBackground(File file, Type type) { final String imageKey; if (mImageKey != null) { @@ -497,14 +501,45 @@ private Bitmap doFileInBackground(File file) { // Not found in disk cache if (thumbnail == null) { - int px = getThumbnailDimension(); + if (Type.IMAGE.equals(type)) { + int px = getThumbnailDimension(); - Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getAbsolutePath(), px, px); + Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getAbsolutePath(), px, px); - if (bitmap != null) { - thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px); + if (bitmap != null) { + thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px); + } + } else if (Type.VIDEO.equals(type)) { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + retriever.setDataSource(file.getAbsolutePath()); + thumbnail = retriever.getFrameAtTime(-1); + } catch (Exception ex) { + // can't create a bitmap + Log_OC.w(TAG, "Failed to create bitmap from video " + file.getAbsolutePath()); + } finally { + try { + retriever.release(); + } catch (RuntimeException ex) { + // Ignore failure at this point. + Log_OC.w(TAG, "Failed release MediaMetadataRetriever for " + file.getAbsolutePath()); + } + } + + if (thumbnail != null) { + // Scale down bitmap if too large. + int px = getThumbnailDimension(); + int width = thumbnail.getWidth(); + int height = thumbnail.getHeight(); + int max = Math.max(width, height); + if (max > px) { + thumbnail = BitmapUtils.scaleBitmap(thumbnail, px, width, height, max); + thumbnail = addThumbnailToCache(imageKey, thumbnail, file.getPath(), px); + } + } } } + return thumbnail; } } diff --git a/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java index 9d4a7608b7bc..4ddc20914503 100644 --- a/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java +++ b/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java @@ -1,22 +1,22 @@ /** - * ownCloud Android client application + * ownCloud Android client application * - * @author LukeOwncloud - * @author David A. Velasco - * @author masensio - * Copyright (C) 2016 ownCloud Inc. + * @author LukeOwncloud + * @author David A. Velasco + * @author masensio + * Copyright (C) 2016 ownCloud Inc. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package com.owncloud.android.datamodel; @@ -26,12 +26,8 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; -import android.os.Build; -import android.support.annotation.RequiresApi; -import com.evernote.android.job.JobManager; -import com.evernote.android.job.JobRequest; -import com.evernote.android.job.util.support.PersistableBundleCompat; +import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.db.OCUpload; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; import com.owncloud.android.db.UploadResult; @@ -39,14 +35,9 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.services.AutoUploadJob; -import java.util.ArrayList; import java.util.Calendar; -import java.util.Collections; -import java.util.List; import java.util.Observable; -import java.util.Set; /** * Database helper for storing list of files to be uploaded, including status @@ -130,6 +121,8 @@ public long storeUpload(OCUpload ocUpload) { cv.put(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER, ocUpload.isCreateRemoteFolder() ? 1 : 0); cv.put(ProviderTableMeta.UPLOADS_LAST_RESULT, ocUpload.getLastResult().getValue()); cv.put(ProviderTableMeta.UPLOADS_CREATED_BY, ocUpload.getCreadtedBy()); + cv.put(ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY, ocUpload.isWhileChargingOnly() ? 1 : 0); + cv.put(ProviderTableMeta.UPLOADS_IS_WIFI_ONLY, ocUpload.isUseWifiOnly() ? 1 : 0); Uri result = getDB().insert(ProviderTableMeta.CONTENT_URI_UPLOADS, cv); @@ -163,9 +156,9 @@ public int updateUpload(OCUpload ocUpload) { cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, ocUpload.getUploadEndTimestamp()); int result = getDB().update(ProviderTableMeta.CONTENT_URI_UPLOADS, - cv, - ProviderTableMeta._ID + "=?", - new String[]{String.valueOf(ocUpload.getUploadId())} + cv, + ProviderTableMeta._ID + "=?", + new String[]{String.valueOf(ocUpload.getUploadId())} ); Log_OC.d(TAG, "updateUpload returns with: " + result + " for file: " + ocUpload.getLocalPath()); @@ -188,15 +181,15 @@ private int updateUploadInternal(Cursor c, UploadStatus status, UploadResult res String path = c.getString(c.getColumnIndex(ProviderTableMeta.UPLOADS_LOCAL_PATH)); Log_OC.v( - TAG, - "Updating " + path + " with status:" + status + " and result:" - + (result == null ? "null" : result.toString()) + " (old:" - + upload.toFormattedString() + ")"); + TAG, + "Updating " + path + " with status:" + status + " and result:" + + (result == null ? "null" : result.toString()) + " (old:" + + upload.toFormattedString() + ")"); upload.setUploadStatus(status); upload.setLastResult(result); upload.setRemotePath(remotePath); - if(localPath != null) { + if (localPath != null) { upload.setLocalPath(localPath); } if (status == UploadStatus.UPLOAD_SUCCEEDED) { @@ -221,7 +214,7 @@ private int updateUploadInternal(Cursor c, UploadStatus status, UploadResult res * @param localPath path of the file to upload in the device storage * @return 1 if file status was updated, else 0. */ - public int updateUploadStatus(long id, UploadStatus status, UploadResult result, String remotePath, + private int updateUploadStatus(long id, UploadStatus status, UploadResult result, String remotePath, String localPath) { //Log_OC.v(TAG, "Updating "+filepath+" with uploadStatus="+status +" and result="+result); @@ -236,7 +229,7 @@ public int updateUploadStatus(long id, UploadStatus status, UploadResult result, if (c.getCount() != 1) { Log_OC.e(TAG, c.getCount() + " items for id=" + id - + " available in UploadDb. Expected 1. Failed to update upload db."); + + " available in UploadDb. Expected 1. Failed to update upload db."); } else { returnValue = updateUploadInternal(c, status, result, remotePath, localPath); } @@ -266,7 +259,7 @@ public void notifyObserversNow() { public int removeUpload(OCUpload upload) { int result = getDB().delete( ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta._ID + "=?" , + ProviderTableMeta._ID + "=?", new String[]{Long.toString(upload.getUploadId())} ); Log_OC.d(TAG, "delete returns " + result + " for upload " + upload); @@ -287,7 +280,7 @@ public int removeUpload(OCUpload upload) { public int removeUpload(String accountName, String remotePath) { int result = getDB().delete( ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=? AND " + ProviderTableMeta.UPLOADS_REMOTE_PATH + "=?" , + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=? AND " + ProviderTableMeta.UPLOADS_REMOTE_PATH + "=?", new String[]{accountName, remotePath} ); Log_OC.d(TAG, "delete returns " + result + " for file " + remotePath + " in " + accountName); @@ -374,68 +367,21 @@ private OCUpload createOCUploadFromCursor(Cursor c) { return upload; } - /** - * Get all uploads which are currently being uploaded or waiting in the queue to be uploaded. - */ - public OCUpload[] getCurrentAndPendingUploads() { + public OCUpload[] getCurrentAndPendingUploadsForCurrentAccount() { + Account account = AccountUtils.getCurrentOwnCloudAccount(mContext); OCUpload[] uploads = getUploads( - ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + " OR " + - ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_FOR_WIFI.getValue() + " OR " + - ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_FOR_CHARGING.getValue(), - null + ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_FOR_WIFI.getValue() + + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.LOCK_FAILED.getValue() + + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + + "==" + UploadResult.DELAYED_FOR_CHARGING.getValue() + + " AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", + new String[]{account.name} ); - // add pending Jobs - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return uploads; - } else { - List result = getPendingJobs(); - Collections.addAll(result, uploads); - return result.toArray(uploads); - } - } - - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) - private List getPendingJobs() { - Set jobRequests = JobManager.create(mContext).getAllJobRequestsForTag(AutoUploadJob.TAG); - - ArrayList list = new ArrayList<>(); - - for (JobRequest ji : jobRequests) { - PersistableBundleCompat extras = ji.getExtras(); - OCUpload upload = new OCUpload(extras.getString("filePath", ""), - extras.getString("remotePath", ""), - extras.getString("account", "")); - - list.add(upload); - } - - return list; - } + return uploads; - public void cancelPendingAutoUploadJobsForAccount(Account account) { - JobManager jobManager = JobManager.create(mContext); - for (JobRequest ji: jobManager.getAllJobRequestsForTag(AutoUploadJob.TAG)) { - if (ji.getExtras().getString(AutoUploadJob.ACCOUNT, "").equalsIgnoreCase(account.name)) { - jobManager.cancel(ji.getJobId()); - } - } - } - - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) - public void cancelPendingJob(String accountName, String remotePath){ - JobManager jobManager = JobManager.create(mContext); - Set jobRequests = jobManager.getAllJobRequests(); - - for (JobRequest ji : jobRequests) { - PersistableBundleCompat extras = ji.getExtras(); - if (remotePath.equalsIgnoreCase(extras.getString("remotePath", "")) && - accountName.equalsIgnoreCase(extras.getString("account", ""))) { - jobManager.cancel(ji.getJobId()); - break; - } - } } /** @@ -447,6 +393,13 @@ public OCUpload[] getFailedUploads() { ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value, null); } + public OCUpload[] getFinishedUploadsForCurrentAccount() { + Account account = AccountUtils.getCurrentOwnCloudAccount(mContext); + + return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND + + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{account.name}); + } + /** * Get all uploads which where successfully completed. */ @@ -455,16 +408,33 @@ public OCUpload[] getFinishedUploads() { return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value, null); } + public OCUpload[] getFailedButNotDelayedUploadsForCurrentAccount() { + Account account = AccountUtils.getCurrentOwnCloudAccount(mContext); + + return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() + + AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", + new String[]{account.name} + ); + } + /** - * Get all failed uploads, except for those that were not performed due to lack of Wifi connection - * @return Array of failed uploads, except for those that were not performed due to lack of Wifi connection. + * Get all failed uploads, except for those that were not performed due to lack of Wifi connection. + * + * @return Array of failed uploads, except for those that were not performed due to lack of Wifi connection. */ public OCUpload[] getFailedButNotDelayedUploads() { return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + AND + - ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND + - ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(), - null + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(), + null ); } @@ -473,13 +443,22 @@ private ContentResolver getDB() { } public long clearFailedButNotDelayedUploads() { + Account account = AccountUtils.getCurrentOwnCloudAccount(mContext); + long result = getDB().delete( - ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + AND + - ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND + - ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(), - null + ProviderTableMeta.CONTENT_URI_UPLOADS, + ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() + + AND + + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", + new String[]{account.name} ); + Log_OC.d(TAG, "delete all failed uploads but those delayed for Wifi"); if (result > 0) { notifyObserversNow(); @@ -488,11 +467,14 @@ public long clearFailedButNotDelayedUploads() { } public long clearSuccessfulUploads() { + Account account = AccountUtils.getCurrentOwnCloudAccount(mContext); long result = getDB().delete( ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta.UPLOADS_STATUS + "=="+ UploadStatus.UPLOAD_SUCCEEDED.value, null + ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND + + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{account.name} ); + Log_OC.d(TAG, "delete all successful uploads"); if (result > 0) { notifyObserversNow(); @@ -501,21 +483,31 @@ public long clearSuccessfulUploads() { } public long clearAllFinishedButNotDelayedUploads() { + Account account = AccountUtils.getCurrentOwnCloudAccount(mContext); - String[] whereArgs = new String[2]; + String[] whereArgs = new String[3]; whereArgs[0] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value); whereArgs[1] = String.valueOf(UploadStatus.UPLOAD_FAILED.value); + whereArgs[2] = account.name; long result = getDB().delete( ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta.UPLOADS_STATUS + "=? OR " + ProviderTableMeta.UPLOADS_STATUS + "=? AND " + - ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND + - ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(), + ProviderTableMeta.UPLOADS_STATUS + "=? OR " + ProviderTableMeta.UPLOADS_STATUS + "=?" + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + + AND + + ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() + + AND + + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", whereArgs ); + Log_OC.d(TAG, "delete all finished uploads"); if (result > 0) { notifyObserversNow(); } + return result; } @@ -528,28 +520,28 @@ public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, Uploa if (uploadResult.isCancelled()) { removeUpload( - upload.getAccount().name, - upload.getRemotePath() + upload.getAccount().name, + upload.getRemotePath() ); } else { String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour()) - ? upload.getStoragePath() : null; + ? upload.getStoragePath() : null; if (uploadResult.isSuccess()) { updateUploadStatus( - upload.getOCUploadId(), - UploadStatus.UPLOAD_SUCCEEDED, - UploadResult.UPLOADED, - upload.getRemotePath(), - localPath + upload.getOCUploadId(), + UploadStatus.UPLOAD_SUCCEEDED, + UploadResult.UPLOADED, + upload.getRemotePath(), + localPath ); } else { updateUploadStatus( - upload.getOCUploadId(), - UploadStatus.UPLOAD_FAILED, - UploadResult.fromOperationResult(uploadResult), - upload.getRemotePath(), - localPath + upload.getOCUploadId(), + UploadStatus.UPLOAD_FAILED, + UploadResult.fromOperationResult(uploadResult), + upload.getRemotePath(), + localPath ); } } @@ -560,14 +552,14 @@ public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, Uploa */ public void updateDatabaseUploadStart(UploadFileOperation upload) { String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour()) - ? upload.getStoragePath() : null; + ? upload.getStoragePath() : null; updateUploadStatus( - upload.getOCUploadId(), - UploadStatus.UPLOAD_IN_PROGRESS, - UploadResult.UNKNOWN, - upload.getRemotePath(), - localPath + upload.getOCUploadId(), + UploadStatus.UPLOAD_IN_PROGRESS, + UploadResult.UNKNOWN, + upload.getRemotePath(), + localPath ); } @@ -576,7 +568,7 @@ public void updateDatabaseUploadStart(UploadFileOperation upload) { * Changes the status of any in progress upload from UploadStatus.UPLOAD_IN_PROGRESS * to UploadStatus.UPLOAD_FAILED * - * @return Number of uploads which status was changed. + * @return Number of uploads which status was changed. */ public int failInProgressUploads(UploadResult fail) { Log_OC.v(TAG, "Updating state of any killed upload"); @@ -584,16 +576,16 @@ public int failInProgressUploads(UploadResult fail) { ContentValues cv = new ContentValues(); cv.put(ProviderTableMeta.UPLOADS_STATUS, UploadStatus.UPLOAD_FAILED.getValue()); cv.put( - ProviderTableMeta.UPLOADS_LAST_RESULT, - fail != null ? fail.getValue() : UploadResult.UNKNOWN.getValue() + ProviderTableMeta.UPLOADS_LAST_RESULT, + fail != null ? fail.getValue() : UploadResult.UNKNOWN.getValue() ); cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, Calendar.getInstance().getTimeInMillis()); int result = getDB().update( - ProviderTableMeta.CONTENT_URI_UPLOADS, - cv, - ProviderTableMeta.UPLOADS_STATUS + "=?", - new String[]{String.valueOf(UploadStatus.UPLOAD_IN_PROGRESS.getValue())} + ProviderTableMeta.CONTENT_URI_UPLOADS, + cv, + ProviderTableMeta.UPLOADS_STATUS + "=?", + new String[]{String.valueOf(UploadStatus.UPLOAD_IN_PROGRESS.getValue())} ); if (result == 0) { @@ -602,15 +594,7 @@ public int failInProgressUploads(UploadResult fail) { Log_OC.w(TAG, Integer.toString(result) + " uploads where abruptly interrupted"); notifyObserversNow(); } - return result; - } - public int removeAccountUploads(Account account) { - Log_OC.v(TAG, "Delete all uploads for account " + account.name); - return getDB().delete( - ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=?", - new String[]{account.name}); + return result; } - } diff --git a/src/main/java/com/owncloud/android/db/OCUpload.java b/src/main/java/com/owncloud/android/db/OCUpload.java index 0b78af00b2d6..962890bd031e 100644 --- a/src/main/java/com/owncloud/android/db/OCUpload.java +++ b/src/main/java/com/owncloud/android/db/OCUpload.java @@ -40,7 +40,6 @@ /** * Stores all information in order to start upload operations. PersistentUploadObject can * be stored persistently by {@link UploadsStorageManager}. - * */ public class OCUpload implements Parcelable { @@ -49,7 +48,7 @@ public class OCUpload implements Parcelable { private long mId; /** - * Absolute path in the local file system to the file to be uploaded + * Absolute path in the local file system to the file to be uploaded. */ private String mLocalPath; @@ -64,7 +63,7 @@ public class OCUpload implements Parcelable { private String mAccountName; /** - * File size + * File size. */ private long mFileSize; @@ -77,14 +76,17 @@ public class OCUpload implements Parcelable { * Overwrite destination file? */ private boolean mForceOverwrite; + /** * Create destination folder? */ private boolean mIsCreateRemoteFolder; + /** * Status of upload (later, in_progress, ...). */ private UploadStatus mUploadStatus; + /** * Result from last upload operation. Can be null. */ @@ -95,14 +97,23 @@ public class OCUpload implements Parcelable { */ private int mCreatedBy; - /* + /** * When the upload ended */ private long mUploadEndTimeStamp; + /** + * Upload only via wifi? + */ + private boolean mIsUseWifiOnly; /** - * Main constructor + * Upload only if phone being charged? + */ + private boolean mIsWhileChargingOnly; + + /** + * Main constructor. * * @param localPath Absolute path in the local file system to the file to be uploaded. * @param remotePath Absolute path in the remote account to set to the uploaded file. @@ -124,9 +135,8 @@ public OCUpload(String localPath, String remotePath, String accountName) { mAccountName = accountName; } - /** - * Convenience constructor to reupload already existing {@link OCFile}s + * Convenience constructor to reupload already existing {@link OCFile}s. * * @param ocFile {@link OCFile} instance to update in the remote server. * @param account ownCloud {@link Account} where ocFile is contained. @@ -135,7 +145,6 @@ public OCUpload(OCFile ocFile, Account account) { this(ocFile.getStoragePath(), ocFile.getRemotePath(), account.name); } - /** * Reset all the fields to default values. */ @@ -151,6 +160,8 @@ private void resetData() { mUploadStatus = UploadStatus.UPLOAD_IN_PROGRESS; mLastResult = UploadResult.UNKNOWN; mCreatedBy = UploadFileOperation.CREATED_BY_USER; + mIsUseWifiOnly = true; + mIsWhileChargingOnly = false; } // Getters & Setters @@ -230,7 +241,6 @@ public void setFileSize(long fileSize) { mFileSize = fileSize; } - /** * @return the mimeType */ @@ -340,6 +350,28 @@ public OCUpload[] newArray(int size) { } }; + /** + * @return the isUseWifiOnly + */ + public boolean isUseWifiOnly() { + return mIsUseWifiOnly; + } + + /** + * @param isUseWifiOnly the isUseWifiOnly to set + */ + public void setUseWifiOnly(boolean isUseWifiOnly) { + this.mIsUseWifiOnly = isUseWifiOnly; + } + + public void setWhileChargingOnly(boolean isWhileChargingOnly) { + this.mIsWhileChargingOnly = isWhileChargingOnly; + } + + public boolean isWhileChargingOnly() { + return mIsWhileChargingOnly; + } + /** * Reconstruct from parcel * @@ -369,9 +401,10 @@ public void readFromParcel(Parcel source) { mLastResult = UploadResult.UNKNOWN; } mCreatedBy = source.readInt(); + mIsUseWifiOnly = (source.readInt() == 1); + mIsWhileChargingOnly = (source.readInt() == 1); } - @Override public int describeContents() { return this.hashCode(); @@ -390,6 +423,8 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mUploadEndTimeStamp); dest.writeString(((mLastResult == null) ? "" : mLastResult.name())); dest.writeInt(mCreatedBy); + dest.writeInt(mIsUseWifiOnly ? 1 : 0); + dest.writeInt(mIsWhileChargingOnly ? 1 : 0); } enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR} diff --git a/src/main/java/com/owncloud/android/db/PreferenceManager.java b/src/main/java/com/owncloud/android/db/PreferenceManager.java index daff294f5913..dc7c6195f639 100644 --- a/src/main/java/com/owncloud/android/db/PreferenceManager.java +++ b/src/main/java/com/owncloud/android/db/PreferenceManager.java @@ -48,6 +48,8 @@ public abstract class PreferenceManager { private static final String PREF__LEGACY_CLEAN = "legacyClean"; private static final String PREF__AUTO_UPLOAD_UPDATE_PATH = "autoUploadPathUpdate"; private static final String PREF__PUSH_TOKEN = "pushToken"; + private static final String PREF__AUTO_UPLOAD_SPLIT_OUT = "autoUploadEntriesSplitOut"; + private static final String PREF__AUTO_UPLOAD_INIT = "autoUploadInit"; public static void setPushToken(Context context, String pushToken) { saveStringPreferenceNow(context, PREF__PUSH_TOKEN, pushToken); @@ -198,6 +200,10 @@ public static void setSortAscending(Context context, boolean ascending) { saveBooleanPreference(context, AUTO_PREF__SORT_ASCENDING, ascending); } + public static boolean getAutoUploadInit(Context context) { + return getDefaultSharedPreferences(context).getBoolean(PREF__AUTO_UPLOAD_INIT, false); + } + /** * Gets the legacy cleaning flag last set. * @@ -218,6 +224,15 @@ public static boolean getAutoUploadPathsUpdate(Context context) { return getDefaultSharedPreferences(context).getBoolean(PREF__AUTO_UPLOAD_UPDATE_PATH, false); } + /** + * Gets the auto upload split out flag last set. + * + * @param context Caller {@link Context}, used to access to shared preferences manager. + * @return ascending order the legacy cleaning flag, default is false + */ + public static boolean getAutoUploadSplitEntries(Context context) { + return getDefaultSharedPreferences(context).getBoolean(PREF__AUTO_UPLOAD_SPLIT_OUT, false); + } /** * Saves the legacy cleaning flag which the user has set last. @@ -229,6 +244,10 @@ public static void setLegacyClean(Context context, boolean legacyClean) { saveBooleanPreference(context, PREF__LEGACY_CLEAN, legacyClean); } + public static void setAutoUploadInit(Context context, boolean autoUploadInit) { + saveBooleanPreference(context, PREF__AUTO_UPLOAD_INIT, autoUploadInit); + } + /** * Saves the legacy cleaning flag which the user has set last. * @@ -239,6 +258,15 @@ public static void setAutoUploadPathsUpdate(Context context, boolean pathUpdate) saveBooleanPreference(context, PREF__AUTO_UPLOAD_UPDATE_PATH, pathUpdate); } + /** + * Saves the flag for split entries magic + * + * @param context Caller {@link Context}, used to access to shared preferences manager. + * @param splitOut flag if it is a auto upload path update + */ + public static void setAutoUploadSplitEntries(Context context, boolean splitOut) { + saveBooleanPreference(context, PREF__AUTO_UPLOAD_SPLIT_OUT, splitOut); + } /** * Gets the uploader behavior which the user has set last. @@ -280,7 +308,7 @@ public static void setGridColumns(Context context, float gridColumns) { saveFloatPreference(context, AUTO_PREF__GRID_COLUMNS, gridColumns); } - public static void saveBooleanPreference(Context context, String key, boolean value) { + private static void saveBooleanPreference(Context context, String key, boolean value) { SharedPreferences.Editor appPreferences = getDefaultSharedPreferences(context.getApplicationContext()).edit(); appPreferences.putBoolean(key, value).apply(); } @@ -301,17 +329,11 @@ private static void saveIntPreference(Context context, String key, int value) { appPreferences.putInt(key, value).apply(); } - public static void saveFloatPreference(Context context, String key, float value) { + private static void saveFloatPreference(Context context, String key, float value) { SharedPreferences.Editor appPreferences = getDefaultSharedPreferences(context.getApplicationContext()).edit(); appPreferences.putFloat(key, value).apply(); } - private static void saveLongPreference(Context context, String key, long value) { - SharedPreferences.Editor appPreferences = getDefaultSharedPreferences(context.getApplicationContext()).edit(); - appPreferences.putLong(key, value); - appPreferences.apply(); - } - public static SharedPreferences getDefaultSharedPreferences(Context context) { return android.preference.PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); } diff --git a/src/main/java/com/owncloud/android/db/ProviderMeta.java b/src/main/java/com/owncloud/android/db/ProviderMeta.java index 764be0943aab..ce4bd5c0f678 100644 --- a/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -32,7 +32,7 @@ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 22; + public static final int DB_VERSION = 23; private ProviderMeta() { } @@ -46,6 +46,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String EXTERNAL_LINKS_TABLE_NAME = "external_links"; public static final String ARBITRARY_DATA_TABLE_NAME = "arbitrary_data"; public static final String VIRTUAL_TABLE_NAME = "virtual"; + public static final String FILESYSTEM_TABLE_NAME = "filesystem"; private static final String CONTENT_PREFIX = "content://"; @@ -68,6 +69,9 @@ static public class ProviderTableMeta implements BaseColumns { public static final Uri CONTENT_URI_ARBITRARY_DATA = Uri.parse(CONTENT_PREFIX + MainApp.getAuthority() + "/arbitrary_data"); public static final Uri CONTENT_URI_VIRTUAL = Uri.parse(CONTENT_PREFIX + MainApp.getAuthority() + "/virtual"); + public static final Uri CONTENT_URI_FILESYSTEM = Uri.parse(CONTENT_PREFIX + + MainApp.getAuthority() + "/filesystem"); + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file"; public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file"; @@ -169,6 +173,8 @@ static public class ProviderTableMeta implements BaseColumns { public static final String UPLOADS_LAST_RESULT = "last_result"; public static final String UPLOADS_CREATED_BY = "created_by"; public static final String UPLOADS_DEFAULT_SORT_ORDER = ProviderTableMeta._ID + " collate nocase desc"; + public static final String UPLOADS_IS_WHILE_CHARGING_ONLY = "is_while_charging_only"; + public static final String UPLOADS_IS_WIFI_ONLY = "is_wifi_only"; // Columns of synced folder table public static final String SYNCED_FOLDER_LOCAL_PATH = "local_path"; @@ -176,6 +182,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String SYNCED_FOLDER_WIFI_ONLY = "wifi_only"; public static final String SYNCED_FOLDER_CHARGING_ONLY = "charging_only"; public static final String SYNCED_FOLDER_ENABLED = "enabled"; + public static final String SYNCED_FOLDER_TYPE = "type"; public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date"; public static final String SYNCED_FOLDER_ACCOUNT = "account"; public static final String SYNCED_FOLDER_UPLOAD_ACTION = "upload_option"; @@ -192,8 +199,17 @@ static public class ProviderTableMeta implements BaseColumns { public static final String ARBITRARY_DATA_KEY = "key"; public static final String ARBITRARY_DATA_VALUE = "value"; + // Columns of virtual public static final String VIRTUAL_TYPE = "type"; public static final String VIRTUAL_OCFILE_ID = "ocfile_id"; + + // Columns of filesystem data table + public static final String FILESYSTEM_FILE_LOCAL_PATH = "local_path"; + public static final String FILESYSTEM_FILE_MODIFIED = "modified_at"; + public static final String FILESYSTEM_FILE_IS_FOLDER = "is_folder"; + public static final String FILESYSTEM_FILE_FOUND_RECENTLY = "found_at"; + public static final String FILESYSTEM_FILE_SENT_FOR_UPLOAD = "upload_triggered"; + public static final String FILESYSTEM_SYNCED_FOLDER_ID = "syncedfolder_id"; } } \ No newline at end of file diff --git a/src/main/java/com/owncloud/android/db/UploadResult.java b/src/main/java/com/owncloud/android/db/UploadResult.java index eee69fbc3c63..41112108fcb4 100644 --- a/src/main/java/com/owncloud/android/db/UploadResult.java +++ b/src/main/java/com/owncloud/android/db/UploadResult.java @@ -35,7 +35,8 @@ public enum UploadResult { DELAYED_FOR_WIFI(9), SERVICE_INTERRUPTED(10), DELAYED_FOR_CHARGING(11), - MAINTENANCE_MODE(12); + MAINTENANCE_MODE(12), + LOCK_FAILED(13); private final int value; @@ -77,6 +78,8 @@ public static UploadResult fromValue(int value) { return DELAYED_FOR_CHARGING; case 12: return MAINTENANCE_MODE; + case 13: + return LOCK_FAILED; } return null; } @@ -120,6 +123,8 @@ public static UploadResult fromOperationResult(RemoteOperationResult result) { return UNKNOWN; case MAINTENANCE_MODE: return MAINTENANCE_MODE; + case LOCK_FAILED: + return LOCK_FAILED; default: return UNKNOWN; } diff --git a/src/main/java/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/main/java/com/owncloud/android/files/InstantUploadBroadcastReceiver.java deleted file mode 100644 index c5a21b55c991..000000000000 --- a/src/main/java/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ /dev/null @@ -1,223 +0,0 @@ -/** - * ownCloud Android client application - * - * @author Bartek Przybylski - * @author David A. Velasco - * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2016 ownCloud Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.owncloud.android.files; - -import android.Manifest; -import android.accounts.Account; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.os.Build; -import android.provider.MediaStore.Images; -import android.provider.MediaStore.Video; -import android.support.v4.content.ContextCompat; - -import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.db.PreferenceManager; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.utils.FileStorageUtils; - - -public class InstantUploadBroadcastReceiver extends BroadcastReceiver { - - private static final String TAG = InstantUploadBroadcastReceiver.class.getName(); - // Image action - // Unofficial action, works for most devices but not HTC. See: https://github.com/owncloud/android/issues/6 - private static final String NEW_PHOTO_ACTION_UNOFFICIAL = "com.android.camera.NEW_PICTURE"; - // Officially supported action since SDK 14: - // http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_PICTURE - private static final String NEW_PHOTO_ACTION = "android.hardware.action.NEW_PICTURE"; - // Video action - // Officially supported action since SDK 14: - // http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_VIDEO - private static final String NEW_VIDEO_ACTION = "android.hardware.action.NEW_VIDEO"; - - /** - * Because we support NEW_PHOTO_ACTION and NEW_PHOTO_ACTION_UNOFFICIAL it can happen that - * handleNewPictureAction is called twice for the same photo. Use this simple static variable to - * remember last uploaded photo to filter duplicates. Must not be null! - */ - static String lastUploadedPhotoPath = ""; - - @Override - public void onReceive(Context context, Intent intent) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - Log_OC.d(TAG, "Received: " + intent.getAction()); - if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) { - handleNewPictureAction(context, intent); - Log_OC.d(TAG, "UNOFFICIAL processed: com.android.camera.NEW_PICTURE"); - } else if (intent.getAction().equals(NEW_PHOTO_ACTION)) { - handleNewPictureAction(context, intent); - Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_PICTURE"); - } else if (intent.getAction().equals(NEW_VIDEO_ACTION)) { - handleNewVideoAction(context, intent); - Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_VIDEO"); - } else { - Log_OC.e(TAG, "Incorrect intent received: " + intent.getAction()); - } - } - } - - private void handleNewPictureAction(Context context, Intent intent) { - Cursor c = null; - String file_path = null; - String file_name = null; - String mime_type = null; - long date_taken = 0; - - Log_OC.i(TAG, "New photo received"); - - if (!PreferenceManager.instantPictureUploadEnabled(context)) { - Log_OC.d(TAG, "Instant picture upload disabled, ignoring new picture"); - return; - } - - Account account = AccountUtils.getCurrentOwnCloudAccount(context); - if (account == null) { - Log_OC.w(TAG, "No account found for instant upload, aborting"); - return; - } - - String[] CONTENT_PROJECTION = { - Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE}; - - // if < Jelly Bean permission must be accepted during installation - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { - int permissionCheck = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE); - - if (android.content.pm.PackageManager.PERMISSION_GRANTED != permissionCheck) { - Log_OC.w(TAG, "Read external storage permission isn't granted, aborting"); - return; - } - } - - c = context.getContentResolver().query(intent.getData(), CONTENT_PROJECTION, null, null, null); - if (!c.moveToFirst()) { - Log_OC.e(TAG, "Couldn't resolve given uri: " + intent.getDataString()); - return; - } - file_path = c.getString(c.getColumnIndex(Images.Media.DATA)); - file_name = c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME)); - mime_type = c.getString(c.getColumnIndex(Images.Media.MIME_TYPE)); - date_taken = System.currentTimeMillis(); - c.close(); - - if (file_path.equals(lastUploadedPhotoPath)) { - Log_OC.d(TAG, "Duplicate detected: " + file_path + ". Ignore."); - return; - } - - lastUploadedPhotoPath = file_path; - Log_OC.d(TAG, "Path: " + file_path + ""); - - new FileUploader.UploadRequester(); - - int behaviour = getUploadBehaviour(context); - Boolean subfolderByDate = PreferenceManager.instantPictureUploadPathUseSubfolders(context); - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - String uploadPathdef = context.getString(R.string.instant_upload_path); - String uploadPath = pref.getString("instant_upload_path", uploadPathdef); - - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.uploadNewFile( - context, - account, - file_path, - FileStorageUtils.getInstantUploadFilePath(uploadPath, file_name, date_taken, subfolderByDate), - behaviour, - mime_type, - true, // create parent folder if not existent - UploadFileOperation.CREATED_AS_INSTANT_PICTURE - ); - } - - private Integer getUploadBehaviour(Context context) { - SharedPreferences appPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context); - String behaviour = appPreferences.getString("prefs_instant_behaviour", "NOTHING"); - - if (behaviour.equalsIgnoreCase("NOTHING")) { - Log_OC.d(TAG, "upload file and do nothing"); - return FileUploader.LOCAL_BEHAVIOUR_FORGET; - } else if (behaviour.equalsIgnoreCase("MOVE")) { - Log_OC.d(TAG, "upload file and move file to oc folder"); - return FileUploader.LOCAL_BEHAVIOUR_MOVE; - } else if (behaviour.equalsIgnoreCase("DELETE")) { - Log_OC.d(TAG, "upload file and delete original file"); - return FileUploader.LOCAL_BEHAVIOUR_DELETE; - } - return FileUploader.LOCAL_BEHAVIOUR_FORGET; - } - - private void handleNewVideoAction(Context context, Intent intent) { - Cursor c = null; - String file_path = null; - String file_name = null; - String mime_type = null; - long date_taken = 0; - - Log_OC.i(TAG, "New video received"); - - if (!PreferenceManager.instantVideoUploadEnabled(context)) { - Log_OC.d(TAG, "Instant video upload disabled, ignoring new video"); - return; - } - - Account account = AccountUtils.getCurrentOwnCloudAccount(context); - if (account == null) { - Log_OC.w(TAG, "No account found for instant upload, aborting"); - return; - } - - String[] CONTENT_PROJECTION = {Video.Media.DATA, Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE, - Video.Media.SIZE}; - c = context.getContentResolver().query(intent.getData(), CONTENT_PROJECTION, null, null, null); - if (!c.moveToFirst()) { - Log_OC.e(TAG, "Couldn't resolve given uri: " + intent.getDataString()); - return; - } - file_path = c.getString(c.getColumnIndex(Video.Media.DATA)); - file_name = c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME)); - mime_type = c.getString(c.getColumnIndex(Video.Media.MIME_TYPE)); - c.close(); - date_taken = System.currentTimeMillis(); - Log_OC.d(TAG, file_path + ""); - - int behaviour = getUploadBehaviour(context); - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.uploadNewFile( - context, - account, - file_path, - FileStorageUtils.getInstantVideoUploadFilePath(context, file_name, date_taken), - behaviour, - mime_type, - true, // create parent folder if not existent - UploadFileOperation.CREATED_AS_INSTANT_VIDEO - ); - } - -} diff --git a/src/main/java/com/owncloud/android/files/services/ConnectivityActionReceiver.java b/src/main/java/com/owncloud/android/files/services/ConnectivityActionReceiver.java deleted file mode 100755 index f6c34399f00f..000000000000 --- a/src/main/java/com/owncloud/android/files/services/ConnectivityActionReceiver.java +++ /dev/null @@ -1,223 +0,0 @@ -/** - * ownCloud Android client application - * - * @author LukeOwncloud - * Copyright (C) 2016 ownCloud Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package com.owncloud.android.files.services; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.NetworkInfo; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.os.Bundle; - -import com.owncloud.android.db.PreferenceManager; -import com.owncloud.android.db.UploadResult; -import com.owncloud.android.lib.common.utils.Log_OC; - -/** - * Receives all connectivity action from Android OS at all times and performs - * required OC actions. For now that are: - Signal connectivity to - * {@link FileUploader}. - * - * Later can be added: - Signal connectivity to download service, deletion - * service, ... - Handle offline mode (cf. - * https://github.com/owncloud/android/issues/162) - * - * Have fun with the comments :S - */ -public class ConnectivityActionReceiver extends BroadcastReceiver { - private static final String TAG = ConnectivityActionReceiver.class.getSimpleName(); - - /** - * Magic keyword, by Google. - * - * {@see http://developer.android.com/intl/es/reference/android/net/wifi/WifiInfo.html#getSSID()} - */ - private static final String UNKNOWN_SSID = ""; - - - @Override - public void onReceive(final Context context, Intent intent) { - // LOG ALL EVENTS: - Log_OC.v(TAG, "action: " + intent.getAction()); - Log_OC.v(TAG, "component: " + intent.getComponent()); - Bundle extras = intent.getExtras(); - if (extras != null) { - for (String key : extras.keySet()) { - Log_OC.v(TAG, "key [" + key + "]: " + extras.get(key)); - } - } else { - Log_OC.v(TAG, "no extras"); - } - - if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED) && - (PreferenceManager.instantPictureUploadEnabled(context) && - PreferenceManager.instantPictureUploadWhenChargingOnly(context)) || - (PreferenceManager.instantVideoUploadEnabled(context) && - PreferenceManager.instantVideoUploadWhenChargingOnly(context)) - ) { - // for the moment, only recovery of instant uploads, similar to behaviour in release 1.9.1 - Log_OC.d(TAG, "Requesting retry of instant uploads (& friends) due to charging"); - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.retryFailedUploads( - context, - null, - UploadResult.DELAYED_FOR_CHARGING // for the rest of enqueued when Wifi fell - ); - } - - /** - * There is an interesting mess to process WifiManager.NETWORK_STATE_CHANGED_ACTION and - * ConnectivityManager.CONNECTIVITY_ACTION in a simple and reliable way. - * - * The former triggers much more events than what we really need to know about Wifi connection. - * - * But there are annoying uncertainties about ConnectivityManager.CONNECTIVITY_ACTION due - * to the deprecation of ConnectivityManager.EXTRA_NETWORK_INFO in API level 14, and the absence - * of ConnectivityManager.EXTRA_NETWORK_TYPE until API level 17. Dear Google, how should we - * handle API levels 14 to 16? - * - * In the end maybe we need to keep in memory the current knowledge about connectivity - * and update it taking into account several Intents received in a row - * - * But first let's try something "simple" to keep a basic retry of instant uploads in - * version 1.9.2, similar to the existent until 1.9.1. To be improved. - */ - if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - NetworkInfo networkInfo = - intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - WifiInfo wifiInfo = - intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); - String bssid = - intent.getStringExtra(WifiManager.EXTRA_BSSID); - if(networkInfo.isConnected() && // not enough; see (*) right below - wifiInfo != null && - !UNKNOWN_SSID.equals(wifiInfo.getSSID().toLowerCase()) && - bssid != null - ) { - Log_OC.d(TAG, "WiFi connected"); - - wifiConnected(context); - } else { - // TODO tons of things to check to conclude disconnection; - // TODO maybe alternative commented below, based on CONNECTIVITY_ACTION is better - Log_OC.d(TAG, "WiFi disconnected ... but don't know if right now"); - } - } - // (*) When WiFi is lost, an Intent with network state CONNECTED and SSID "" is - // received right before another Intent with network state DISCONNECTED; needs to - // be differentiated of a new Wifi connection. - // - // Besides, with a new connection two Intents are received, having only the second the extra - // WifiManager.EXTRA_BSSID, with the BSSID of the access point accessed. - // - // Not sure if this protocol is exact, since it's not documented. Only found mild references in - // - http://developer.android.com/intl/es/reference/android/net/wifi/WifiInfo.html#getSSID() - // - http://developer.android.com/intl/es/reference/android/net/wifi/WifiManager.html#EXTRA_BSSID - // and reproduced in Nexus 5 with Android 6. - - - /** - * Possible alternative attending ConnectivityManager.CONNECTIVITY_ACTION. - * - * Let's see what QA has to say - * - if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - NetworkInfo networkInfo = intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO // deprecated in API 14 - ); - int networkType = intent.getIntExtra( - ConnectivityManager.EXTRA_NETWORK_TYPE, // only from API level 17 - -1 - ); - boolean couldBeWifiAction = - (networkInfo == null && networkType < 0) || // cases of lack of info - networkInfo.getType() == ConnectivityManager.TYPE_WIFI || - networkType == ConnectivityManager.TYPE_WIFI; - - if (couldBeWifiAction) { - if (ConnectivityUtils.isAppConnectedViaUnmeteredWiFi(context)) { - Log_OC.d(TAG, "WiFi connected"); - wifiConnected(context); - } else { - Log_OC.d(TAG, "WiFi disconnected"); - wifiDisconnected(context); - } - } /* else, CONNECTIVIY_ACTION is (probably) about other network interface (mobile, bluetooth, ...) - } - */ - } - - private void wifiConnected(Context context) { - // for the moment, only recovery of instant uploads, similar to behaviour in release 1.9.1 - if ( - (PreferenceManager.instantPictureUploadEnabled(context) && - PreferenceManager.instantPictureUploadViaWiFiOnly(context)) || - (PreferenceManager.instantVideoUploadEnabled(context) && - PreferenceManager.instantVideoUploadViaWiFiOnly(context)) - ) { - Log_OC.d(TAG, "Requesting retry of instant uploads (& friends)"); - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.retryFailedUploads( - context, - null, - UploadResult.NETWORK_CONNECTION // for the interrupted when Wifi fell, if any - // (side effect: any upload failed due to network error will be retried too, instant or not) - ); - requester.retryFailedUploads( - context, - null, - UploadResult.DELAYED_FOR_WIFI // for the rest of enqueued when Wifi fell - ); - } - } - - /** - * - private void wifiDisconnected() { - // TODO something smart - - // NOTE: explicit cancellation of only-wifi instant uploads is not needed anymore, since currently: - // - any upload in progress will be interrupted due to the lack of connectivity while the device - // reconnects through other network interface; - // - FileUploader checks instant upload settings and connection state before executing each - // upload operation, so other pending instant uploads after the current one will not be run - // (currently are silently moved to FAILED state) - } - - - - static public void enableActionReceiver(Context context) { - PackageManager pm = context.getPackageManager(); - ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class); - pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, - PackageManager.DONT_KILL_APP); - } - - static public void disableActionReceiver(Context context) { - PackageManager pm = context.getPackageManager(); - ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class); - pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } - - */ -} \ No newline at end of file diff --git a/src/main/java/com/owncloud/android/files/services/FileUploader.java b/src/main/java/com/owncloud/android/files/services/FileUploader.java index e98e71d2466f..7e1d4de507a9 100644 --- a/src/main/java/com/owncloud/android/files/services/FileUploader.java +++ b/src/main/java/com/owncloud/android/files/services/FileUploader.java @@ -34,7 +34,6 @@ import android.content.Context; import android.content.Intent; import android.os.Binder; -import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -45,6 +44,9 @@ import android.support.v4.app.NotificationCompat; import android.util.Pair; +import com.evernote.android.job.JobRequest; +import com.evernote.android.job.util.Device; +import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.authentication.AuthenticatorActivity; @@ -77,6 +79,8 @@ import java.util.Map; import java.util.Vector; +import javax.annotation.Nullable; + /** * Service for uploading files. Invoke using context.startService(...). * @@ -144,6 +148,9 @@ public class FileUploader extends Service * Key to signal what is the origin of the upload request */ public static final String KEY_CREATED_BY = "CREATED_BY"; + + public static final String KEY_WHILE_ON_WIFI_ONLY = "KEY_ON_WIFI_ONLY"; + /** * Set to true if upload is to performed only when phone is being charged. */ @@ -194,7 +201,6 @@ public void onRenameUpload() { sendBroadcastUploadStarted(mCurrentUpload); } - /** * Helper class providing methods to ease requesting commands to {@link FileUploader} . * @@ -214,7 +220,37 @@ public void uploadNewFile( String[] mimeTypes, Integer behaviour, Boolean createRemoteFolder, - int createdBy + int createdBy, + boolean requiresWifi, + boolean requiresCharging + ) { + Intent intent = new Intent(context, FileUploader.class); + + intent.putExtra(FileUploader.KEY_ACCOUNT, account); + intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths); + intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths); + intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes); + intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour); + intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder); + intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy); + intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi); + intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging); + + context.startService(intent); + } + + public void uploadFileWithOverwrite( + Context context, + Account account, + String[] localPaths, + String[] remotePaths, + String[] mimeTypes, + Integer behaviour, + Boolean createRemoteFolder, + int createdBy, + boolean requiresWifi, + boolean requiresCharging, + boolean overwrite ) { Intent intent = new Intent(context, FileUploader.class); @@ -225,15 +261,41 @@ public void uploadNewFile( intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour); intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder); intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy); + intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi); + intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging); + intent.putExtra(FileUploader.KEY_FORCE_OVERWRITE, overwrite); context.startService(intent); } + /** + * Call to upload a file + */ + public void uploadFileWithOverwrite(Context context, Account account, String localPath, String remotePath, int + behaviour, String mimeType, boolean createRemoteFile, int createdBy, boolean requiresWifi, + boolean requiresCharging, boolean overwrite) { + + uploadFileWithOverwrite( + context, + account, + new String[]{localPath}, + new String[]{remotePath}, + new String[]{mimeType}, + behaviour, + createRemoteFile, + createdBy, + requiresWifi, + requiresCharging, + overwrite + ); + } + /** * Call to upload a new single file */ public void uploadNewFile(Context context, Account account, String localPath, String remotePath, int - behaviour, String mimeType, boolean createRemoteFile, int createdBy) { + behaviour, String mimeType, boolean createRemoteFile, int createdBy, boolean requiresWifi, + boolean requiresCharging) { uploadNewFile( context, @@ -243,7 +305,9 @@ public void uploadNewFile(Context context, Account account, String localPath, St new String[]{mimeType}, behaviour, createRemoteFile, - createdBy + createdBy, + requiresWifi, + requiresCharging ); } @@ -288,7 +352,6 @@ public void retry (Context context, OCUpload upload) { } } - /** * Retry a subset of all the stored failed uploads. * @@ -306,7 +369,7 @@ public void retryFailedUploads(Context context, Account account, UploadResult up boolean accountMatch; for ( OCUpload failedUpload: failedUploads) { accountMatch = (account == null || account.name.equals(failedUpload.getAccountName())); - resultMatch = (uploadResult == null || uploadResult.equals(failedUpload.getLastResult())); + resultMatch = ((uploadResult == null || uploadResult.equals(failedUpload.getLastResult()))); if (accountMatch && resultMatch) { if (currentAccount == null || !currentAccount.name.equals(failedUpload.getAccountName())) { @@ -330,13 +393,12 @@ private void retry(Context context, Account account, OCUpload upload) { i.putExtra(FileUploader.KEY_RETRY, true); i.putExtra(FileUploader.KEY_ACCOUNT, account); i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload); + context.startService(i); } } - } - /** * Service initialization */ @@ -370,7 +432,6 @@ public void onCreate() { am.addOnAccountsUpdatedListener(this, null, false); } - /** * Service clean-up when restarted after being killed */ @@ -379,7 +440,6 @@ private void resurrection() { mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); } - /** * Service clean up */ @@ -399,7 +459,6 @@ public void onDestroy() { super.onDestroy(); } - /** * Entry point to add one or several files to the queue of uploads. * @@ -426,9 +485,14 @@ public int onStartCommand(Intent intent, int flags, int startId) { return Service.START_NOT_STICKY; } OwnCloudVersion ocv = AccountUtils.getServerVersion(account); + boolean chunked = ocv.isChunkedUploadSupported(); + boolean onWifiOnly = intent.getBooleanExtra(KEY_WHILE_ON_WIFI_ONLY, false); + boolean whileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, false); + if (!retry) { + if (!(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) { Log_OC.e(TAG, "Not enough information provided in intent"); @@ -451,7 +515,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE); } - boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET); @@ -503,10 +566,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { ocUpload.setCreateRemoteFolder(isCreateRemoteFolder); ocUpload.setCreatedBy(createdBy); ocUpload.setLocalAction(localAction); - /*ocUpload.setUseWifiOnly(isUseWifiOnly); - ocUpload.setWhileChargingOnly(isWhileChargingOnly);*/ + ocUpload.setUseWifiOnly(onWifiOnly); + ocUpload.setWhileChargingOnly(whileChargingOnly); + ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS); + newUpload = new UploadFileOperation( account, files[i], @@ -514,7 +579,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { chunked, forceOverwrite, localAction, - this + this, + onWifiOnly, + whileChargingOnly ); newUpload.setCreatedBy(createdBy); if (isCreateRemoteFolder) { @@ -561,6 +628,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { } OCUpload upload = intent.getParcelableExtra(KEY_RETRY_UPLOAD); + onWifiOnly = upload.isUseWifiOnly(); + whileChargingOnly = upload.isWhileChargingOnly(); + UploadFileOperation newUpload = new UploadFileOperation( account, null, @@ -568,7 +638,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { chunked, upload.isForceOverwrite(), // TODO should be read from DB? upload.getLocalAction(), // TODO should be read from DB? - this + this, + onWifiOnly, + whileChargingOnly ); newUpload.addDatatransferProgressListener(this); @@ -634,9 +706,8 @@ public void onAccountsUpdated(Account[] accounts) { } /** - * Binder to let client components to perform operations on the queue of - * uploads. - *

+ * Binder to let client components to perform operations on the queue of uploads. + * * It provides by itself the available operations. */ public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener { @@ -645,9 +716,7 @@ public class FileUploaderBinder extends Binder implements OnDatatransferProgress * Map of listeners that will be reported about progress of uploads from a * {@link FileUploaderBinder} instance */ - private Map mBoundListeners = - new HashMap(); - + private Map mBoundListeners = new HashMap<>(); /** * Cancels a pending or current upload of a remote file. @@ -656,7 +725,7 @@ public class FileUploaderBinder extends Binder implements OnDatatransferProgress * @param file A file in the queue of pending uploads */ public void cancel(Account account, OCFile file) { - cancel(account.name, file.getRemotePath()); + cancel(account.name, file.getRemotePath(), null); } /** @@ -665,7 +734,7 @@ public void cancel(Account account, OCFile file) { * @param storedUpload Upload operation persisted */ public void cancel(OCUpload storedUpload) { - cancel(storedUpload.getAccountName(), storedUpload.getRemotePath()); + cancel(storedUpload.getAccountName(), storedUpload.getRemotePath(), null); } @@ -674,8 +743,10 @@ public void cancel(OCUpload storedUpload) { * * @param accountName Local name of an ownCloud account where the remote file will be stored. * @param remotePath Remote target of the upload + * + * Setting result code will pause rather than cancel the job */ - private void cancel(String accountName, String remotePath) { + private void cancel(String accountName, String remotePath, @Nullable ResultCode resultCode ) { Pair removeResult = mPendingUploads.remove(accountName, remotePath); UploadFileOperation upload = removeResult.first; @@ -686,14 +757,17 @@ private void cancel(String accountName, String remotePath) { upload = mCurrentUpload; } + if (upload != null) { upload.cancel(); // need to update now table in mUploadsStorageManager, // since the operation will not get to be run by FileUploader#uploadFile - mUploadsStorageManager.removeUpload(accountName, remotePath); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - // try to cancel job in jobScheduler - mUploadsStorageManager.cancelPendingJob(accountName, remotePath); + if (resultCode != null) { + mUploadsStorageManager.updateDatabaseUploadResult(new RemoteOperationResult(resultCode), upload); + notifyUploadResult(upload, new RemoteOperationResult(resultCode)); + } else { + mUploadsStorageManager.removeUpload(accountName, remotePath); + } } } @@ -740,7 +814,6 @@ public boolean isUploading(Account account, OCFile file) { return (mPendingUploads.contains(account.name, file.getRemotePath())); } - public boolean isUploadingNow(OCUpload upload) { return ( upload != null && @@ -751,7 +824,6 @@ public boolean isUploadingNow(OCUpload upload) { ); } - /** * Adds a listener interested in the progress of the upload for a concrete file. * @@ -771,7 +843,6 @@ public void addDatatransferProgressListener( mBoundListeners.put(targetKey, listener); } - /** * Adds a listener interested in the progress of the upload for a concrete file. * @@ -789,7 +860,6 @@ public void addDatatransferProgressListener( mBoundListeners.put(targetKey, listener); } - /** * Removes a listener interested in the progress of the upload for a concrete file. * @@ -811,7 +881,6 @@ public void removeDatatransferProgressListener( } } - /** * Removes a listener interested in the progress of the upload for a concrete file. * @@ -831,7 +900,6 @@ public void removeDatatransferProgressListener( } } - @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { @@ -840,12 +908,24 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, if (boundListener != null) { boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName); + + if (MainApp.getAppContext() != null) { + if (mCurrentUpload.getIsWifiRequired() && !Device.getNetworkType(MainApp.getAppContext()). + equals(JobRequest.NetworkType.UNMETERED)) { + cancel(mCurrentUpload.getAccount().name, mCurrentUpload.getFile().getRemotePath() + , ResultCode.DELAYED_FOR_WIFI); + } else if (mCurrentUpload.getIsChargingRequired() && + !Device.isCharging(MainApp.getAppContext())) { + cancel(mCurrentUpload.getAccount().name, mCurrentUpload.getFile().getRemotePath() + , ResultCode.DELAYED_FOR_CHARGING); + } + } } } /** * Builds a key for the map of listeners. - *

+ * * TODO use method in IndexedForest, or refactor both to a common place * add to local database) to better policy (add to local database, then upload) * @@ -863,7 +943,7 @@ private String buildRemoteName(String accountName, String remotePath) { /** * Upload worker. Performs the pending uploads in the order they were * requested. - *

+ * * Created with the Looper of a new thread, started in * {@link FileUploader#onCreate()}. */ @@ -958,7 +1038,7 @@ public void uploadFile(String uploadKey) { mCurrentAccount.name, mCurrentUpload.getOldFile().getRemotePath() ); - /** TODO: grant that name is also updated for mCurrentUpload.getOCUploadId */ + // TODO: grant that name is also updated for mCurrentUpload.getOCUploadId } else { removeResult = mPendingUploads.removePayload( @@ -973,7 +1053,6 @@ public void uploadFile(String uploadKey) { notifyUploadResult(mCurrentUpload, uploadResult); sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second); - } // generate new Thumbnail @@ -997,8 +1076,7 @@ public void uploadFile(String uploadKey) { private void notifyUploadStart(UploadFileOperation upload) { // / create status notification with a progress bar mLastPercent = 0; - mNotificationBuilder = - NotificationUtils.newNotificationBuilder(this); + mNotificationBuilder = NotificationUtils.newNotificationBuilder(this); mNotificationBuilder .setOngoing(true) .setSmallIcon(R.drawable.notification_icon) @@ -1022,7 +1100,6 @@ private void notifyUploadStart(UploadFileOperation upload) { } // else wait until the upload really start (onTransferProgress is called), so that if it's discarded // due to lack of Wifi, no notification is shown // TODO generalize for automated uploads - } /** @@ -1058,7 +1135,8 @@ private void notifyUploadResult(UploadFileOperation upload, if (!uploadResult.isCancelled() && !ResultCode.LOCAL_FILE_NOT_FOUND.equals(uploadResult.getCode()) && !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_WIFI) && - !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_CHARGING)) { + !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_CHARGING) && + !uploadResult.getCode().equals(ResultCode.LOCK_FAILED) ) { int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker : R.string.uploader_upload_failed_ticker; @@ -1077,9 +1155,7 @@ private void notifyUploadResult(UploadFileOperation upload, .setOngoing(false) .setProgress(0, 0, false); - content = ErrorMessageAdapter.getErrorCauseMessage( - uploadResult, upload, getResources() - ); + content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, upload, getResources()); if (needsToUpdateCredentials) { // let the user update credentials with one click @@ -1130,7 +1206,6 @@ uploadResult, upload, getResources() } } - /** * Sends a broadcast in order to the interested activities can update their * view @@ -1144,7 +1219,6 @@ private void sendBroadcastUploadsAdded() { sendStickyBroadcast(start); } - /** * Sends a broadcast in order to the interested activities can update their * view @@ -1208,5 +1282,4 @@ private void cancelUploadsForAccount(Account account) { mPendingUploads.remove(account.name); mUploadsStorageManager.removeUploads(account.name); } - } diff --git a/src/main/java/com/owncloud/android/services/AccountRemovalJob.java b/src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java similarity index 96% rename from src/main/java/com/owncloud/android/services/AccountRemovalJob.java rename to src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java index 0414f6b59043..97024961f500 100644 --- a/src/main/java/com/owncloud/android/services/AccountRemovalJob.java +++ b/src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java @@ -19,7 +19,7 @@ * along with this program. If not, see . */ -package com.owncloud.android.services; +package com.owncloud.android.jobs; import android.accounts.Account; import android.accounts.AccountManager; @@ -76,7 +76,7 @@ protected Result onRunJob(Params params) { // remove pending account removal ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver()); - arbitraryDataProvider.deleteKeyForAccount(account, PENDING_FOR_REMOVAL); + arbitraryDataProvider.deleteKeyForAccount(account.name, PENDING_FOR_REMOVAL); return Result.SUCCESS; } else { diff --git a/src/main/java/com/owncloud/android/services/ContactsBackupJob.java b/src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java similarity index 97% rename from src/main/java/com/owncloud/android/services/ContactsBackupJob.java rename to src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java index a81140d0ee2f..15a30c85c464 100644 --- a/src/main/java/com/owncloud/android/services/ContactsBackupJob.java +++ b/src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java @@ -19,7 +19,7 @@ * along with this program. If not, see . */ -package com.owncloud.android.services; +package com.owncloud.android.jobs; import android.accounts.Account; import android.content.ComponentName; @@ -44,6 +44,7 @@ import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.operations.UploadFileOperation; +import com.owncloud.android.services.OperationsService; import com.owncloud.android.ui.activity.ContactsPreferenceActivity; import java.io.File; @@ -97,7 +98,7 @@ protected Result onRunJob(Params params) { OperationsService.BIND_AUTO_CREATE); // store execution date - arbitraryDataProvider.storeOrUpdateKeyValue(account, + arbitraryDataProvider.storeOrUpdateKeyValue(account.name, ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP, String.valueOf(Calendar.getInstance().getTimeInMillis())); } else { @@ -156,7 +157,9 @@ private void backupContact(Account account, String backupFolder) { FileUploader.LOCAL_BEHAVIOUR_MOVE, null, true, - UploadFileOperation.CREATED_BY_USER + UploadFileOperation.CREATED_BY_USER, + false, + false ); } catch (Exception e) { diff --git a/src/main/java/com/owncloud/android/services/ContactsImportJob.java b/src/main/java/com/owncloud/android/jobs/ContactsImportJob.java similarity index 99% rename from src/main/java/com/owncloud/android/services/ContactsImportJob.java rename to src/main/java/com/owncloud/android/jobs/ContactsImportJob.java index 3380d34d6290..247902711588 100644 --- a/src/main/java/com/owncloud/android/services/ContactsImportJob.java +++ b/src/main/java/com/owncloud/android/jobs/ContactsImportJob.java @@ -19,7 +19,7 @@ * along with this program. If not, see . */ -package com.owncloud.android.services; +package com.owncloud.android.jobs; import android.database.Cursor; import android.net.Uri; diff --git a/src/main/java/com/owncloud/android/jobs/FilesSyncJob.java b/src/main/java/com/owncloud/android/jobs/FilesSyncJob.java new file mode 100644 index 000000000000..216cf3e7a8de --- /dev/null +++ b/src/main/java/com/owncloud/android/jobs/FilesSyncJob.java @@ -0,0 +1,153 @@ +/** + * Nextcloud Android client application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * Copyright (C) 2017 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ + +package com.owncloud.android.jobs; + +import android.accounts.Account; +import android.content.ContentResolver; +import android.content.Context; +import android.os.PowerManager; +import android.support.annotation.NonNull; +import android.support.media.ExifInterface; +import android.text.TextUtils; + +import com.evernote.android.job.Job; +import com.evernote.android.job.util.support.PersistableBundleCompat; +import com.owncloud.android.MainApp; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.FilesystemDataProvider; +import com.owncloud.android.datamodel.MediaFolderType; +import com.owncloud.android.datamodel.SyncedFolder; +import com.owncloud.android.datamodel.SyncedFolderProvider; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.operations.UploadFileOperation; +import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.FilesSyncHelper; +import com.owncloud.android.utils.MimeTypeUtil; + +import java.io.File; +import java.io.IOException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/* + Job that: + - restarts existing jobs if required + - finds new and modified files since we last run this + - creates upload tasks + */ +public class FilesSyncJob extends Job { + public static final String TAG = "FilesSyncJob"; + + public static final String SKIP_CUSTOM = "skipCustom"; + + @NonNull + @Override + protected Result onRunJob(Params params) { + final Context context = MainApp.getAppContext(); + final ContentResolver contentResolver = context.getContentResolver(); + + PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); + PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + TAG); + wakeLock.acquire(); + + PersistableBundleCompat bundle = params.getExtras(); + final boolean skipCustom = bundle.getBoolean(SKIP_CUSTOM, false); + + FilesSyncHelper.restartJobsIfNeeded(); + FilesSyncHelper.insertAllDBEntries(skipCustom); + + // Create all the providers we'll need + final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver); + SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver); + + for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { + if ((syncedFolder.isEnabled()) && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.getType())) { + for (String path : filesystemDataProvider.getFilesForUpload(syncedFolder.getLocalPath(), + Long.toString(syncedFolder.getId()))) { + File file = new File(path); + + Long lastModificationTime = file.lastModified(); + final Locale currentLocale = context.getResources().getConfiguration().locale; + + if (MediaFolderType.IMAGE == syncedFolder.getType()) { + String mimetypeString = FileStorageUtils.getMimeTypeFromName(file.getAbsolutePath()); + if ("image/jpeg".equalsIgnoreCase(mimetypeString) || "image/tiff". + equalsIgnoreCase(mimetypeString)) { + try { + ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath()); + String exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME); + if (!TextUtils.isEmpty(exifDate)) { + ParsePosition pos = new ParsePosition(0); + SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", + currentLocale); + sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID())); + Date dateTime = sFormatter.parse(exifDate, pos); + lastModificationTime = dateTime.getTime(); + } + + } catch (IOException e) { + Log_OC.d(TAG, "Failed to get the proper time " + e.getLocalizedMessage()); + } + } + } + + boolean needsCharging = syncedFolder.getChargingOnly(); + boolean needsWifi = syncedFolder.getWifiOnly(); + + String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath()); + + Account account = AccountUtils.getOwnCloudAccountByName(context, syncedFolder.getAccount()); + + requester.uploadFileWithOverwrite( + context, + account, + file.getAbsolutePath(), + FileStorageUtils.getInstantUploadFilePath( + currentLocale, + syncedFolder.getRemotePath(), file.getName(), + lastModificationTime, + syncedFolder.getSubfolderByDate()), + syncedFolder.getUploadAction(), + mimeType, + true, // create parent folder if not existent + UploadFileOperation.CREATED_AS_INSTANT_PICTURE, + needsWifi, + needsCharging, + true + ); + + filesystemDataProvider.updateFilesystemFileAsSentForUpload(path, + Long.toString(syncedFolder.getId())); + } + } + } + + wakeLock.release(); + return Result.SUCCESS; + } +} diff --git a/src/main/java/com/owncloud/android/services/NCJobCreator.java b/src/main/java/com/owncloud/android/jobs/NCJobCreator.java similarity index 92% rename from src/main/java/com/owncloud/android/services/NCJobCreator.java rename to src/main/java/com/owncloud/android/jobs/NCJobCreator.java index c3d2cd418256..2fdf85ca9265 100644 --- a/src/main/java/com/owncloud/android/services/NCJobCreator.java +++ b/src/main/java/com/owncloud/android/jobs/NCJobCreator.java @@ -18,7 +18,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package com.owncloud.android.services; +package com.owncloud.android.jobs; import com.evernote.android.job.Job; import com.evernote.android.job.JobCreator; @@ -31,14 +31,14 @@ public class NCJobCreator implements JobCreator { @Override public Job create(String tag) { switch (tag) { - case AutoUploadJob.TAG: - return new AutoUploadJob(); case ContactsBackupJob.TAG: return new ContactsBackupJob(); case ContactsImportJob.TAG: return new ContactsImportJob(); case AccountRemovalJob.TAG: return new AccountRemovalJob(); + case FilesSyncJob.TAG: + return new FilesSyncJob(); default: return null; } diff --git a/src/main/java/com/owncloud/android/jobs/NContentObserverJob.java b/src/main/java/com/owncloud/android/jobs/NContentObserverJob.java new file mode 100644 index 000000000000..7e5fc85417f1 --- /dev/null +++ b/src/main/java/com/owncloud/android/jobs/NContentObserverJob.java @@ -0,0 +1,69 @@ +/** + * Nextcloud Android client application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * Copyright (C) 2017 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ + +package com.owncloud.android.jobs; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.os.Build; +import android.support.annotation.RequiresApi; + +import com.evernote.android.job.JobRequest; +import com.evernote.android.job.util.support.PersistableBundleCompat; +import com.owncloud.android.utils.FilesSyncHelper; + +import java.util.concurrent.TimeUnit; + +/* + Job that triggers new FilesSyncJob in case new photo or video were detected + */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class NContentObserverJob extends JobService { + @Override + public boolean onStartJob(JobParameters params) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (params.getJobId() == FilesSyncHelper.ContentSyncJobId && params.getTriggeredContentAuthorities() + != null && params.getTriggeredContentUris() != null + && params.getTriggeredContentUris().length > 0) { + + PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat(); + persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true); + + new JobRequest.Builder(FilesSyncJob.TAG) + .setExecutionWindow(1, TimeUnit.SECONDS.toMillis(2)) + .setBackoffCriteria(TimeUnit.SECONDS.toMillis(5), JobRequest.BackoffPolicy.LINEAR) + .setUpdateCurrent(false) + .build() + .schedule(); + } + + FilesSyncHelper.scheduleNJobs(true); + } + + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +} diff --git a/src/main/java/com/owncloud/android/operations/MoveFileOperation.java b/src/main/java/com/owncloud/android/operations/MoveFileOperation.java index 856d37f2ec2f..3ffb86b263d5 100644 --- a/src/main/java/com/owncloud/android/operations/MoveFileOperation.java +++ b/src/main/java/com/owncloud/android/operations/MoveFileOperation.java @@ -20,8 +20,6 @@ package com.owncloud.android.operations; -import android.accounts.Account; - import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -41,17 +39,14 @@ public class MoveFileOperation extends SyncOperation { private String mTargetParentPath; private OCFile mFile; - - /** * Constructor * * @param srcPath Remote path of the {@link OCFile} to move. * @param targetParentPath Path to the folder where the file will be moved into. - * @param account OwnCloud account containing both the file and the target folder */ - public MoveFileOperation(String srcPath, String targetParentPath, Account account) { + public MoveFileOperation(String srcPath, String targetParentPath) { mSrcPath = srcPath; mTargetParentPath = targetParentPath; if (!mTargetParentPath.endsWith(OCFile.PATH_SEPARATOR)) { @@ -68,7 +63,7 @@ public MoveFileOperation(String srcPath, String targetParentPath, Account accoun */ @Override protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result = null; + RemoteOperationResult result; /// 1. check move validity if (mTargetParentPath.startsWith(mSrcPath)) { diff --git a/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index 30918f158fdb..a3d57224efad 100644 --- a/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -203,7 +203,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (result.isSuccess()) { // request for the synchronization of KEPT-IN-SYNC file contents - startContentSynchronizations(mFilesToSyncContents, client); + startContentSynchronizations(mFilesToSyncContents); } } @@ -453,12 +453,9 @@ private void synchronizeData(ArrayList folderAndFiles) { * on. * * @param filesToSyncContents Synchronization operations to execute. - * @param client Interface to the remote ownCloud server. */ - private void startContentSynchronizations( - List filesToSyncContents, OwnCloudClient client - ) { - RemoteOperationResult contentsResult = null; + private void startContentSynchronizations(List filesToSyncContents) { + RemoteOperationResult contentsResult; for (SynchronizeFileOperation op: filesToSyncContents) { contentsResult = op.execute(mStorageManager, mContext); // async if (!contentsResult.isSuccess()) { @@ -487,7 +484,7 @@ private void startContentSynchronizations( * the operation. */ private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) { - RemoteOperationResult result = null; + RemoteOperationResult result; // remote request GetRemoteSharesForFileOperation operation = diff --git a/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java b/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java index 4d1e939089d9..c63ae0c33bec 100644 --- a/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -469,19 +469,6 @@ private void startContentSynchronizations(List filesToSyncContent } } - - /** - * Creates and populates a new {@link com.owncloud.android.datamodel.OCFile} - * object with the data read from the server. - * - * @param remote remote file read from the server (remote file or folder). - * @return New OCFile instance representing the remote resource described by we. - */ - private OCFile fillOCFile(RemoteFile remote) { - return FileStorageUtils.fillOCFile(remote); - } - - /** * Scans the default location for saving local copies of files searching for * a 'lost' file with the same full name as the {@link com.owncloud.android.datamodel.OCFile} diff --git a/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 10bc08a9fdd6..0e4d343aa496 100644 --- a/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -1,21 +1,20 @@ /** - * ownCloud Android client application + * ownCloud Android client application * - * @author David A. Velasco - * Copyright (C) 2016 ownCloud GmbH. + * @author David A. Velasco + * Copyright (C) 2016 ownCloud GmbH. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package com.owncloud.android.operations; @@ -24,11 +23,12 @@ import android.content.Context; import android.net.Uri; +import com.evernote.android.job.JobRequest; +import com.evernote.android.job.util.Device; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.db.OCUpload; -import com.owncloud.android.db.PreferenceManager; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; @@ -44,7 +44,6 @@ import com.owncloud.android.lib.resources.files.RemoteFile; import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation; import com.owncloud.android.operations.common.SyncOperation; -import com.owncloud.android.utils.ConnectivityUtils; import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.MimeType; import com.owncloud.android.utils.MimeTypeUtil; @@ -52,14 +51,20 @@ import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.RequestEntity; +import org.lukhnos.nnio.file.Files; +import org.lukhnos.nnio.file.Paths; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.RandomAccessFile; import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -94,6 +99,8 @@ public class UploadFileOperation extends SyncOperation { private boolean mForceOverwrite = false; private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY; private int mCreatedBy = CREATED_BY_USER; + private boolean mOnWifiOnly = false; + private boolean mWhileChargingOnly = false; private boolean mWasRenamed = false; private long mOCUploadId = -1; @@ -147,7 +154,9 @@ public UploadFileOperation(Account account, boolean chunked, boolean forceOverwrite, int localBehaviour, - Context context + Context context, + boolean onWifiOnly, + boolean whileChargingOnly ) { if (account == null) { throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " + "creation"); @@ -171,6 +180,8 @@ public UploadFileOperation(Account account, } else { mFile = file; } + mOnWifiOnly = onWifiOnly; + mWhileChargingOnly = whileChargingOnly; mRemotePath = upload.getRemotePath(); mChunked = chunked; mForceOverwrite = forceOverwrite; @@ -182,6 +193,14 @@ public UploadFileOperation(Account account, mRemoteFolderToBeCreated = upload.isCreateRemoteFolder(); } + public boolean getIsWifiRequired() { + return mOnWifiOnly; + } + + public boolean getIsChargingRequired() { + return mWhileChargingOnly; + } + public Account getAccount() { return mAccount; } @@ -237,7 +256,7 @@ public void setCreatedBy(int createdBy) { } } - public int getCreatedBy () { + public int getCreatedBy() { return mCreatedBy; } @@ -249,9 +268,10 @@ public boolean isInstantVideo() { return mCreatedBy == CREATED_AS_INSTANT_VIDEO; } - public void setOCUploadId(long id){ + public void setOCUploadId(long id) { mOCUploadId = id; } + public long getOCUploadId() { return mOCUploadId; } @@ -260,14 +280,14 @@ public Set getDataTransferListeners() { return mDataTransferListeners; } - public void addDatatransferProgressListener (OnDatatransferProgressListener listener) { + public void addDatatransferProgressListener(OnDatatransferProgressListener listener) { synchronized (mDataTransferListeners) { mDataTransferListeners.add(listener); } if (mEntity != null) { - ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener); + ((ProgressiveDataTransferer) mEntity).addDatatransferProgressListener(listener); } - if(mUploadOperation != null){ + if (mUploadOperation != null) { mUploadOperation.addDatatransferProgressListener(listener); } } @@ -277,14 +297,14 @@ public void removeDatatransferProgressListener(OnDatatransferProgressListener li mDataTransferListeners.remove(listener); } if (mEntity != null) { - ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener); + ((ProgressiveDataTransferer) mEntity).removeDatatransferProgressListener(listener); } - if(mUploadOperation != null){ + if (mUploadOperation != null) { mUploadOperation.removeDatatransferProgressListener(listener); } } - public void addRenameUploadListener (OnRenameListener listener) { + public void addRenameUploadListener(OnRenameListener listener) { mRenameUploadListener = listener; } @@ -297,17 +317,18 @@ protected RemoteOperationResult run(OwnCloudClient client) { File temporalFile = null; File originalFile = new File(mOriginalStoragePath); File expectedFile = null; + FileLock fileLock = null; try { /// Check that connectivity conditions are met and delays the upload otherwise - if (delayForWifi()) { + if (mOnWifiOnly && !Device.getNetworkType(mContext).equals(JobRequest.NetworkType.UNMETERED)) { Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath()); return new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI); } // Check if charging conditions are met and delays the upload otherwise - if (delayForCharging()){ + if (mWhileChargingOnly && !Device.isCharging(mContext)) { Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath()); return new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING); } @@ -372,20 +393,21 @@ protected RemoteOperationResult run(OwnCloudClient client) { } // Get the last modification date of the file from the file system - Long timeStampLong = originalFile.lastModified()/1000; + Long timeStampLong = originalFile.lastModified() / 1000; String timeStamp = timeStampLong.toString(); /// perform the upload - if ( mChunked && + if (mChunked && (new File(mFile.getStoragePath())).length() > - ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) { + ChunkedUploadRemoteFileOperation.CHUNK_SIZE) { mUploadOperation = new ChunkedUploadRemoteFileOperation(mContext, mFile.getStoragePath(), mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict(), timeStamp); } else { mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(), mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict(), timeStamp); } - Iterator listener = mDataTransferListeners.iterator(); + + Iterator listener = mDataTransferListeners.iterator(); while (listener.hasNext()) { mUploadOperation.addDatatransferProgressListener(listener.next()); } @@ -394,23 +416,77 @@ protected RemoteOperationResult run(OwnCloudClient client) { throw new OperationCancelledException(); } + FileChannel channel = null; + try { + channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel(); + fileLock = channel.tryLock(); + } catch (FileNotFoundException e) { + if (temporalFile == null) { + String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); + mFile.setStoragePath(temporalPath); + temporalFile = new File(temporalPath); + + result = copy(originalFile, temporalFile); + + if (result != null) { + return result; + } else { + if (temporalFile.length() == originalFile.length()) { + channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel(); + fileLock = channel.tryLock(); + } else { + while (temporalFile.length() != originalFile.length()) { + Files.deleteIfExists(Paths.get(temporalPath)); + result = copy(originalFile, temporalFile); + + if (result != null) { + return result; + } else { + channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw"). + getChannel(); + fileLock = channel.tryLock(); + } + } + } + } + } else { + channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel(); + fileLock = channel.tryLock(); + } + } + result = mUploadOperation.execute(client); /// move local temporal file or original file to its corresponding // location in the ownCloud local folder - if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED ) { + if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) { result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); } + } catch (FileNotFoundException e) { + Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore"); + result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND); + } catch (OverlappingFileLockException e) { + Log_OC.d(TAG, "Overlapping file lock exception"); + result = new RemoteOperationResult(ResultCode.LOCK_FAILED); } catch (Exception e) { result = new RemoteOperationResult(e); } finally { mUploadStarted.set(false); + + if (fileLock != null) { + try { + fileLock.release(); + } catch (IOException e) { + Log_OC.e(TAG, "Failed to unlock file with path " + mOriginalStoragePath); + } + } + if (temporalFile != null && !originalFile.equals(temporalFile)) { temporalFile.delete(); } - if (result == null){ + if (result == null) { result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); } if (result.isSuccess()) { @@ -418,7 +494,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { result.getLogMessage()); } else { if (result.getException() != null) { - if(result.isCancelled()){ + if (result.isCancelled()) { Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage()); } else { @@ -478,41 +554,6 @@ protected RemoteOperationResult run(OwnCloudClient client) { return result; } - - /** - * Checks origin of current upload and network type to decide if should be delayed, according to - * current user preferences. - * - * @return 'True' if the upload was delayed until WiFi connectivity is available, 'false' otherwise. - */ - private boolean delayForWifi() { - boolean delayInstantPicture = ( - isInstantPicture() && PreferenceManager.instantPictureUploadViaWiFiOnly(mContext) - ); - boolean delayInstantVideo = ( - isInstantVideo() && PreferenceManager.instantVideoUploadViaWiFiOnly(mContext) - ); - return ( - (delayInstantPicture || delayInstantVideo) && - !ConnectivityUtils.isAppConnectedViaUnmeteredWiFi(mContext) - ); - } - - /** - * Check if upload should be delayed due to not charging - * - * @return 'True' if the upload was delayed until device is charging, 'false' otherwise. - */ - private boolean delayForCharging() { - boolean delayInstantPicture = isInstantPicture() && - PreferenceManager.instantPictureUploadWhenChargingOnly(mContext); - - boolean delayInstantVideo = isInstantVideo() && PreferenceManager.instantVideoUploadWhenChargingOnly(mContext); - - return ((delayInstantPicture || delayInstantVideo) && !ConnectivityUtils.isCharging(mContext)); - } - - /** * Checks the existence of the folder where the current file will be uploaded both * in the remote server and in the local database. @@ -566,7 +607,8 @@ private OCFile createLocalFolder(String remotePath) { /** * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false. - * New file is stored as mFile, original as mOldFile. + * New file is stored as mFile, original as mOldFile. + * * @param newRemotePath new remote path */ private void createNewOCFile(String newRemotePath) { @@ -615,8 +657,7 @@ private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) { suffix = " (" + count + ")"; if (pos >= 0) { check = existsFile(wc, remotePath + suffix + "." + extension); - } - else { + } else { check = existsFile(wc, remotePath + suffix); } count++; @@ -629,7 +670,7 @@ private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) { } } - private boolean existsFile(OwnCloudClient client, String remotePath){ + private boolean existsFile(OwnCloudClient client, String remotePath) { ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, mContext, false); RemoteOperationResult result = existsOperation.execute(client); @@ -667,10 +708,10 @@ public boolean isUploadInProgress() { * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult}, * TODO use Exceptions instead * - * @param sourceFile Source file to copy. - * @param targetFile Target location to copy the file. - * @return {@link RemoteOperationResult} - * @throws IOException + * @param sourceFile Source file to copy. + * @param targetFile Target location to copy the file. + * @return {@link RemoteOperationResult} + * @throws IOException */ private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException { Log_OC.d(TAG, "Copying local file"); @@ -758,10 +799,10 @@ private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOEx * * TODO refactor both this and 'copy' in a single method * - * @param sourceFile Source file to move. - * @param targetFile Target location to move the file. - * @return {@link RemoteOperationResult} - * @throws IOException + * @param sourceFile Source file to move. + * @param targetFile Target location to move the file. + * @return {@link RemoteOperationResult} + * @throws IOException */ private void move(File sourceFile, File targetFile) throws IOException { @@ -769,8 +810,8 @@ private void move(File sourceFile, File targetFile) throws IOException { File expectedFolder = targetFile.getParentFile(); expectedFolder.mkdirs(); - if (expectedFolder.isDirectory()){ - if (!sourceFile.renameTo(targetFile)){ + if (expectedFolder.isDirectory()) { + if (!sourceFile.renameTo(targetFile)) { // try to copy and then delete targetFile.createNewFile(); FileChannel inChannel = new FileInputStream(sourceFile).getChannel(); @@ -778,12 +819,11 @@ private void move(File sourceFile, File targetFile) throws IOException { try { inChannel.transferTo(0, inChannel.size(), outChannel); sourceFile.delete(); - } catch (Exception e){ + } catch (Exception e) { mFile.setStoragePath(""); // forget the local file // by now, treat this as a success; the file was uploaded // the best option could be show a warning message - } - finally { + } finally { if (inChannel != null) { inChannel.close(); } @@ -847,7 +887,7 @@ private void saveUploadedFile(OwnCloudClient client) { // generate new Thumbnail final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), mAccount); + new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), mAccount); task.execute(file); } diff --git a/src/main/java/org/nextcloud/providers/DocumentsStorageProvider.java b/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java similarity index 99% rename from src/main/java/org/nextcloud/providers/DocumentsStorageProvider.java rename to src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java index ca94302ec1e7..07dd5a93d93e 100644 --- a/src/main/java/org/nextcloud/providers/DocumentsStorageProvider.java +++ b/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java @@ -18,7 +18,7 @@ * */ -package org.nextcloud.providers; +package com.owncloud.android.providers; import android.accounts.Account; import android.annotation.TargetApi; diff --git a/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/src/main/java/com/owncloud/android/providers/FileContentProvider.java index 2a6b9d85e611..883649e00042 100644 --- a/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -74,6 +74,7 @@ public class FileContentProvider extends ContentProvider { private static final int EXTERNAL_LINKS = 8; private static final int ARBITRARY_DATA = 9; private static final int VIRTUAL = 10; + private static final int FILESYSTEM = 11; private static final String TAG = FileContentProvider.class.getSimpleName(); @@ -209,6 +210,9 @@ private int delete(SQLiteDatabase db, Uri uri, String where, String[] whereArgs) case VIRTUAL: count = db.delete(ProviderTableMeta.VIRTUAL_TABLE_NAME, where, whereArgs); break; + case FILESYSTEM: + count = db.delete(ProviderTableMeta.FILESYSTEM_TABLE_NAME, where, whereArgs); + break; default: //Log_OC.e(TAG, "Unknown uri " + uri); throw new IllegalArgumentException("Unknown uri: " + uri.toString()); @@ -312,7 +316,6 @@ private Uri insert(SQLiteDatabase db, Uri uri, ContentValues values) { if (uploadId > 0) { insertedUploadUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_UPLOADS, uploadId); - trimSuccessfulUploads(db); } else { throw new SQLException(ERROR + uri); @@ -365,6 +368,16 @@ private Uri insert(SQLiteDatabase db, Uri uri, ContentValues values) { } return insertedVirtualUri; + case FILESYSTEM: + Uri insertedFilesystemUri = null; + long filesystedId = db.insert(ProviderTableMeta.FILESYSTEM_TABLE_NAME, null, values); + if (filesystedId > 0) { + insertedFilesystemUri = + ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILESYSTEM, filesystedId); + } else { + throw new SQLException("ERROR " + uri); + } + return insertedFilesystemUri; default: throw new IllegalArgumentException("Unknown uri id: " + uri); } @@ -417,6 +430,7 @@ public boolean onCreate() { mUriMatcher.addURI(authority, "external_links", EXTERNAL_LINKS); mUriMatcher.addURI(authority, "arbitrary_data", ARBITRARY_DATA); mUriMatcher.addURI(authority, "virtual", VIRTUAL); + mUriMatcher.addURI(authority, "filesystem", FILESYSTEM); return true; } @@ -518,6 +532,13 @@ private Cursor query( sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1)); } break; + case FILESYSTEM: + sqlQuery.setTables(ProviderTableMeta.FILESYSTEM_TABLE_NAME); + if (uri.getPathSegments().size() > 1) { + sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + + uri.getPathSegments().get(1)); + } + break; default: throw new IllegalArgumentException("Unknown uri id: " + uri); } @@ -549,6 +570,9 @@ private Cursor query( default: // Files order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER; break; + case FILESYSTEM: + order = ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH; + break; } } else { order = sortOrder; @@ -594,12 +618,13 @@ private int update( return db.update(ProviderTableMeta.CAPABILITIES_TABLE_NAME, values, selection, selectionArgs); case UPLOADS: int ret = db.update(ProviderTableMeta.UPLOADS_TABLE_NAME, values, selection, selectionArgs); - trimSuccessfulUploads(db); return ret; case SYNCED_FOLDERS: return db.update(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, values, selection, selectionArgs); case ARBITRARY_DATA: return db.update(ProviderTableMeta.ARBITRARY_DATA_TABLE_NAME, values, selection, selectionArgs); + case FILESYSTEM: + return db.update(ProviderTableMeta.FILESYSTEM_TABLE_NAME, values, selection, selectionArgs); default: return db.update(ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs); } @@ -662,6 +687,9 @@ public void onCreate(SQLiteDatabase db) { // Create virtual table createVirtualTable(db); + + // Create filesystem table + createFileSystemTable(db); } @Override @@ -669,14 +697,14 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log_OC.i(SQL, "Entering in onUpgrade"); boolean upgraded = false; if (oldVersion == 1 && newVersion >= 2) { - Log_OC.i(SQL, "Entering in the #1 ADD in onUpgrade"); + Log_OC.i(SQL, "Entering in the #2 ADD in onUpgrade"); db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + ADD_COLUMN + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " + " DEFAULT 0"); upgraded = true; } if (oldVersion < 3 && newVersion >= 3) { - Log_OC.i(SQL, "Entering in the #2 ADD in onUpgrade"); + Log_OC.i(SQL, "Entering in the #3 ADD in onUpgrade"); db.beginTransaction(); try { db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + @@ -696,7 +724,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } if (oldVersion < 4 && newVersion >= 4) { - Log_OC.i(SQL, "Entering in the #3 ADD in onUpgrade"); + Log_OC.i(SQL, "Entering in the #4 ADD in onUpgrade"); db.beginTransaction(); try { db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + @@ -720,7 +748,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } if (oldVersion < 5 && newVersion >= 5) { - Log_OC.i(SQL, "Entering in the #4 ADD in onUpgrade"); + Log_OC.i(SQL, "Entering in the #5 ADD in onUpgrade"); db.beginTransaction(); try { db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + @@ -738,7 +766,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } if (oldVersion < 6 && newVersion >= 6) { - Log_OC.i(SQL, "Entering in the #5 ADD in onUpgrade"); + Log_OC.i(SQL, "Entering in the #6 ADD in onUpgrade"); db.beginTransaction(); try { db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + @@ -843,6 +871,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.endTransaction(); } } + if (!upgraded) { Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); } @@ -937,7 +966,6 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } finally { db.endTransaction(); } - } if (!upgraded) { @@ -1033,6 +1061,43 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (!upgraded) { Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); } + + if (oldVersion < 23 && newVersion >= 23) { + Log_OC.i(SQL, "Entering in the #23 adding type column for synced folders, Create filesystem table"); + db.beginTransaction(); + try { + // add type column default being CUSTOM (0) + Log_OC.i(SQL, "Add type column and default value 0 (CUSTOM) to synced_folders table"); + db.execSQL(ALTER_TABLE + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.SYNCED_FOLDER_TYPE + + " INTEGER " + " DEFAULT 0"); + + Log_OC.i(SQL, "Add charging and wifi columns to uploads"); + db.execSQL(ALTER_TABLE + ProviderTableMeta.UPLOADS_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.UPLOADS_IS_WIFI_ONLY + + " INTEGER " + " DEFAULT 0"); + + db.execSQL(ALTER_TABLE + ProviderTableMeta.UPLOADS_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY + + " INTEGER " + " DEFAULT 0"); + + // create Filesystem table + Log_OC.i(SQL, "Create filesystem table"); + createFileSystemTable(db); + + upgraded = true; + db.setTransactionSuccessful(); + + } catch (Throwable t) { + Log_OC.e(TAG, "ERROR!", t); + } finally { + db.endTransaction(); + } + + if (!upgraded) { + Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); + } + } } } @@ -1135,6 +1200,8 @@ private void createUploadsTable(SQLiteDatabase db) { + ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + INTEGER // boolean + ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + INTEGER + ProviderTableMeta.UPLOADS_LAST_RESULT + INTEGER // Upload LastResult + + ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY + INTEGER // boolean + + ProviderTableMeta.UPLOADS_IS_WIFI_ONLY + INTEGER // boolean + ProviderTableMeta.UPLOADS_CREATED_BY + " INTEGER );" // Upload createdBy ); @@ -1159,7 +1226,8 @@ private void createSyncedFoldersTable(SQLiteDatabase db) { + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account - + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER );" // upload action + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER, " // upload action + + ProviderTableMeta.SYNCED_FOLDER_TYPE + " INTEGER );" // type ); } @@ -1170,7 +1238,7 @@ private void createExternalLinksTable(SQLiteDatabase db) { + ProviderTableMeta.EXTERNAL_LINKS_LANGUAGE + " TEXT, " // language + ProviderTableMeta.EXTERNAL_LINKS_TYPE + " INTEGER, " // type + ProviderTableMeta.EXTERNAL_LINKS_NAME + " TEXT, " // name - + ProviderTableMeta.EXTERNAL_LINKS_URL + " TEXT )" // url + + ProviderTableMeta.EXTERNAL_LINKS_URL + " TEXT );" // url ); } @@ -1179,7 +1247,7 @@ private void createArbitraryData(SQLiteDatabase db) { + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " // id + ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " TEXT, " // cloud id (account name + FQDN) + ProviderTableMeta.ARBITRARY_DATA_KEY + " TEXT, " // key - + ProviderTableMeta.ARBITRARY_DATA_VALUE + " TEXT) " // value + + ProviderTableMeta.ARBITRARY_DATA_VALUE + " TEXT );" // value ); } @@ -1191,6 +1259,19 @@ private void createVirtualTable(SQLiteDatabase db) { ); } + private void createFileSystemTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + ProviderTableMeta.FILESYSTEM_TABLE_NAME + "(" + + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " // id + + ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH + " TEXT, " + + ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER + " INTEGER, " + + ProviderTableMeta.FILESYSTEM_FILE_FOUND_RECENTLY + " LONG, " + + ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD + " INTEGER, " + + ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID + " STRING, " + + ProviderTableMeta.FILESYSTEM_FILE_MODIFIED + " LONG );" + ); + } + + /** * Version 10 of database does not modify its scheme. It coincides with the upgrade of the ownCloud account names * structure to include in it the path to the server instance. Updating the account names and path to local files diff --git a/src/main/java/com/owncloud/android/services/AdvancedFileAlterationListener.java b/src/main/java/com/owncloud/android/services/AdvancedFileAlterationListener.java deleted file mode 100644 index 9c8fa7fcc571..000000000000 --- a/src/main/java/com/owncloud/android/services/AdvancedFileAlterationListener.java +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Nextcloud Android client application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - *

- * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - *

- * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - *

- * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.owncloud.android.services; - -import android.content.Context; -import android.content.res.Resources; -import android.media.ExifInterface; -import android.os.Handler; -import android.text.TextUtils; - -import com.evernote.android.job.JobRequest; -import com.evernote.android.job.util.support.PersistableBundleCompat; -import com.owncloud.android.MainApp; -import com.owncloud.android.R; -import com.owncloud.android.datamodel.ArbitraryDataProvider; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.activity.Preferences; -import com.owncloud.android.utils.FileStorageUtils; - -import org.apache.commons.io.monitor.FileAlterationListener; -import org.apache.commons.io.monitor.FileAlterationObserver; - -import java.io.File; -import java.io.IOException; -import java.text.ParsePosition; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; - -/** - * Magical file alteration listener - */ - -public class AdvancedFileAlterationListener implements FileAlterationListener { - - public static final String TAG = "AdvancedFileAlterationListener"; - public static final int DELAY_INVOCATION_MS = 2500; - private Context context; - private boolean lightVersion; - - private SyncedFolder syncedFolder; - - private Map uploadMap = new HashMap<>(); - private Handler handler = new Handler(); - - public AdvancedFileAlterationListener(SyncedFolder syncedFolder, boolean lightVersion) { - super(); - - context = MainApp.getAppContext(); - this.lightVersion = lightVersion; - this.syncedFolder = syncedFolder; - } - - @Override - public void onStart(FileAlterationObserver observer) { - // This method is intentionally empty - } - - @Override - public void onDirectoryCreate(File directory) { - // This method is intentionally empty - } - - @Override - public void onDirectoryChange(File directory) { - // This method is intentionally empty - } - - @Override - public void onDirectoryDelete(File directory) { - // This method is intentionally empty - } - - @Override - public void onFileCreate(final File file) { - onFileCreate(file, DELAY_INVOCATION_MS); - } - - public void onFileCreate(final File file, int delay) { - if (file != null) { - uploadMap.put(file.getAbsolutePath(), null); - - String mimetypeString = FileStorageUtils.getMimeTypeFromName(file.getAbsolutePath()); - Long lastModificationTime = file.lastModified(); - final Locale currentLocale = context.getResources().getConfiguration().locale; - - if ("image/jpeg".equalsIgnoreCase(mimetypeString) || "image/tiff".equalsIgnoreCase(mimetypeString)) { - try { - ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath()); - String exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME); - if (!TextUtils.isEmpty(exifDate)) { - ParsePosition pos = new ParsePosition(0); - SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale); - sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID())); - Date dateTime = sFormatter.parse(exifDate, pos); - lastModificationTime = dateTime.getTime(); - } - - } catch (IOException e) { - Log_OC.d(TAG, "Failed to get the proper time " + e.getLocalizedMessage()); - } - } - - - final Long finalLastModificationTime = lastModificationTime; - - Runnable runnable = new Runnable() { - @Override - public void run() { - - String remotePath; - boolean subfolderByDate; - boolean chargingOnly; - boolean wifiOnly; - Integer uploadAction; - String accountName = syncedFolder.getAccount(); - - if (lightVersion) { - ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context - .getContentResolver()); - - Resources resources = MainApp.getAppContext().getResources(); - - remotePath = resources.getString(R.string.syncedFolder_remote_folder) + OCFile.PATH_SEPARATOR + - new File(syncedFolder.getLocalPath()).getName() + OCFile.PATH_SEPARATOR; - subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders); - chargingOnly = resources.getBoolean(R.bool.syncedFolder_light_on_charging); - wifiOnly = arbitraryDataProvider.getBooleanValue(accountName, - Preferences.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI); - String uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour); - uploadAction = getUploadAction(uploadActionString); - } else { - remotePath = syncedFolder.getRemotePath(); - subfolderByDate = syncedFolder.getSubfolderByDate(); - chargingOnly = syncedFolder.getChargingOnly(); - wifiOnly = syncedFolder.getWifiOnly(); - uploadAction = syncedFolder.getUploadAction(); - } - - PersistableBundleCompat bundle = new PersistableBundleCompat(); - bundle.putString(AutoUploadJob.LOCAL_PATH, file.getAbsolutePath()); - bundle.putString(AutoUploadJob.REMOTE_PATH, FileStorageUtils.getInstantUploadFilePath( - currentLocale, - remotePath, file.getName(), - finalLastModificationTime, - subfolderByDate)); - bundle.putString(AutoUploadJob.ACCOUNT, accountName); - bundle.putInt(AutoUploadJob.UPLOAD_BEHAVIOUR, uploadAction); - - new JobRequest.Builder(AutoUploadJob.TAG) - .setExecutionWindow(30_000L, 80_000L) - .setRequiresCharging(chargingOnly) - .setRequiredNetworkType(wifiOnly ? JobRequest.NetworkType.UNMETERED : - JobRequest.NetworkType.ANY) - .setExtras(bundle) - .setPersisted(false) - .setRequirementsEnforced(true) - .setUpdateCurrent(false) - .build() - .schedule(); - - uploadMap.remove(file.getAbsolutePath()); - } - }; - - uploadMap.put(file.getAbsolutePath(), runnable); - handler.postDelayed(runnable, delay); - } - } - - private Integer getUploadAction(String action) { - switch (action) { - case "LOCAL_BEHAVIOUR_FORGET": - return FileUploader.LOCAL_BEHAVIOUR_FORGET; - case "LOCAL_BEHAVIOUR_MOVE": - return FileUploader.LOCAL_BEHAVIOUR_MOVE; - case "LOCAL_BEHAVIOUR_DELETE": - return FileUploader.LOCAL_BEHAVIOUR_DELETE; - default: - return FileUploader.LOCAL_BEHAVIOUR_FORGET; - } - } - - @Override - public void onFileChange(File file) { - onFileChange(file, 2500); - } - - public void onFileChange(File file, int delay) { - Runnable runnable; - if ((runnable = uploadMap.get(file.getAbsolutePath())) != null) { - handler.removeCallbacks(runnable); - handler.postDelayed(runnable, delay); - } - } - - @Override - public void onFileDelete(File file) { - Runnable runnable; - if ((runnable = uploadMap.get(file.getAbsolutePath())) != null) { - handler.removeCallbacks(runnable); - uploadMap.remove(file.getAbsolutePath()); - } - } - - @Override - public void onStop(FileAlterationObserver observer) { - // This method is intentionally empty - } - - public int getActiveTasksCount() { - return uploadMap.size(); - } -} diff --git a/src/main/java/com/owncloud/android/services/AutoUploadJob.java b/src/main/java/com/owncloud/android/services/AutoUploadJob.java deleted file mode 100644 index 7ae6502f6e4e..000000000000 --- a/src/main/java/com/owncloud/android/services/AutoUploadJob.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Nextcloud Android client application - * - * @author Tobias Kaminsky - * Copyright (C) 2017 Mario Danic - * Copyright (C) 2016 Tobias Kaminsky - * Copyright (C) 2016 Nextcloud - *

- * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - *

- * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - *

- * You should have received a copy of the GNU Affero General Public - * License along with this program. If not, see . - */ - -package com.owncloud.android.services; - -import android.accounts.Account; -import android.content.Context; -import android.support.annotation.NonNull; - -import com.evernote.android.job.Job; -import com.evernote.android.job.util.support.PersistableBundleCompat; -import com.owncloud.android.MainApp; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.utils.MimeTypeUtil; - -import java.io.File; - -public class AutoUploadJob extends Job { - public static final String TAG = "AutoUploadJob"; - - public static final String LOCAL_PATH = "filePath"; - public static final String REMOTE_PATH = "remotePath"; - public static final String ACCOUNT = "account"; - public static final String UPLOAD_BEHAVIOUR = "uploadBehaviour"; - - @NonNull - @Override - protected Result onRunJob(Params params) { - final Context context = MainApp.getAppContext(); - PersistableBundleCompat bundle = params.getExtras(); - final String filePath = bundle.getString(LOCAL_PATH, ""); - final String remotePath = bundle.getString(REMOTE_PATH, ""); - final Account account = AccountUtils.getOwnCloudAccountByName(context, bundle.getString(ACCOUNT, "")); - final Integer uploadBehaviour = bundle.getInt(UPLOAD_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_FORGET); - - - File file = new File(filePath); - - - // File can be deleted between job generation and job execution. If file does not exist, just ignore it - if (file.exists()) { - final String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath()); - - final FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.uploadNewFile( - context, - account, - filePath, - remotePath, - uploadBehaviour, - mimeType, - true, // create parent folder if not existent - UploadFileOperation.CREATED_AS_INSTANT_PICTURE - ); - } - - - return Result.SUCCESS; - } -} diff --git a/src/main/java/com/owncloud/android/services/OperationsService.java b/src/main/java/com/owncloud/android/services/OperationsService.java index 512a4a7f63a5..f9f48dc8a3ae 100644 --- a/src/main/java/com/owncloud/android/services/OperationsService.java +++ b/src/main/java/com/owncloud/android/services/OperationsService.java @@ -678,7 +678,7 @@ remotePath, account, syncFileContents, getApplicationContext() // Move file/folder String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH); - operation = new MoveFileOperation(remotePath, newParentPath, account); + operation = new MoveFileOperation(remotePath, newParentPath); } else if (action.equals(ACTION_COPY_FILE)) { // Copy file/folder diff --git a/src/main/java/com/owncloud/android/services/observer/AdvancedFileAlterationObserver.java b/src/main/java/com/owncloud/android/services/observer/AdvancedFileAlterationObserver.java deleted file mode 100644 index 86046b94596f..000000000000 --- a/src/main/java/com/owncloud/android/services/observer/AdvancedFileAlterationObserver.java +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Original source code: - * https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/monitor/FileAlterationObserver.java - * - * Modified by Mario Danic - * Changes are Copyright (C) 2017 Mario Danic - * Copyright (C) 2017 Nextcloud GmbH - * - * All changes are under the same licence as the original. - * - */ -package com.owncloud.android.services.observer; - -import android.os.SystemClock; - -import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.services.AdvancedFileAlterationListener; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.comparator.NameFileComparator; -import org.apache.commons.io.monitor.FileAlterationObserver; -import org.apache.commons.io.monitor.FileEntry; - -import java.io.File; -import java.io.FileFilter; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -public class AdvancedFileAlterationObserver extends FileAlterationObserver implements Serializable { - - private static final long serialVersionUID = 1185122225658782848L; - private static final int DELAY_INVOCATION_MS = 2500; - private final List listeners = new CopyOnWriteArrayList<>(); - private FileEntry rootEntry; - private FileFilter fileFilter; - private Comparator comparator; - private SyncedFolder syncedFolder; - - private static final FileEntry[] EMPTY_ENTRIES = new FileEntry[0]; - - public AdvancedFileAlterationObserver(SyncedFolder syncedFolder, FileFilter fileFilter) { - super(syncedFolder.getLocalPath(), fileFilter); - - this.rootEntry = new FileEntry(new File(syncedFolder.getLocalPath())); - this.fileFilter = fileFilter; - this.syncedFolder = syncedFolder; - comparator = NameFileComparator.NAME_SYSTEM_COMPARATOR; - } - - public long getSyncedFolderID() { - return syncedFolder.getId(); - } - - public SyncedFolder getSyncedFolder() { - return syncedFolder; - } - - /** - * Return the directory being observed. - * - * @return the directory being observed - */ - public File getDirectory() { - return rootEntry.getFile(); - } - - /** - * Return the fileFilter. - * - * @return the fileFilter - * @since 2.1 - */ - public FileFilter getFileFilter() { - return fileFilter; - } - - public FileEntry getRootEntry() { - return rootEntry; - } - - public void setRootEntry(FileEntry rootEntry) { - this.rootEntry = rootEntry; - } - - /** - * Add a file system listener. - * - * @param listener The file system listener - */ - public void addListener(final AdvancedFileAlterationListener listener) { - if (listener != null) { - listeners.add(listener); - } - } - - /** - * Remove a file system listener. - * - * @param listener The file system listener - */ - public void removeListener(final AdvancedFileAlterationListener listener) { - if (listener != null) { - while (listeners.remove(listener)) { - } - } - } - - /** - * Returns the set of registered file system listeners. - * - * @return The file system listeners - */ - public Iterable getMagicListeners() { - return listeners; - } - - /** - * Does nothing - hack for the monitor - * - * - */ - public void initialize() { - // does nothing - hack the monitor - } - - - /** - * Initializes everything - * - * @throws Exception if an error occurs - */ - public void init() throws Exception { - rootEntry.refresh(rootEntry.getFile()); - final FileEntry[] children = doListFiles(rootEntry.getFile(), rootEntry); - rootEntry.setChildren(children); - } - - - /** - * Final processing. - * - * @throws Exception if an error occurs - */ - public void destroy() throws Exception { - Iterator iterator = getMagicListeners().iterator(); - while (iterator.hasNext()) { - AdvancedFileAlterationListener AdvancedFileAlterationListener = (AdvancedFileAlterationListener) iterator.next(); - while (AdvancedFileAlterationListener.getActiveTasksCount() > 0) { - SystemClock.sleep(250); - } - } - } - - public void checkAndNotifyNow() { - /* fire onStart() */ - for (final AdvancedFileAlterationListener listener : listeners) { - listener.onStart(this); - } - - /* fire directory/file events */ - final File rootFile = rootEntry.getFile(); - if (rootFile.exists()) { - checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootFile), 0); - } else if (rootEntry.isExists()) { - try { - // try to init once more - init(); - if (rootEntry.getFile().exists()) { - checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootEntry.getFile()), 0); - } else { - checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, 0); - } - } catch (Exception e) { - Log_OC.d("AdvancedFileAlterationObserver", "Failed getting an observer to intialize " + e); - checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, 0); - } - } // else didn't exist and still doesn't - - /* fire onStop() */ - for (final AdvancedFileAlterationListener listener : listeners) { - listener.onStop(this); - } - } - - /** - * Check whether the file and its children have been created, modified or deleted. - */ - public void checkAndNotify() { - - /* fire onStart() */ - for (final AdvancedFileAlterationListener listener : listeners) { - listener.onStart(this); - } - - /* fire directory/file events */ - final File rootFile = rootEntry.getFile(); - if (rootFile.exists()) { - checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootFile), DELAY_INVOCATION_MS); - } else if (rootEntry.isExists()) { - try { - // try to init once more - init(); - if (rootEntry.getFile().exists()) { - checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootEntry.getFile()), - DELAY_INVOCATION_MS); - } else { - checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, DELAY_INVOCATION_MS); - } - } catch (Exception e) { - Log_OC.d("AdvancedFileAlterationObserver", "Failed getting an observer to intialize " + e); - checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, DELAY_INVOCATION_MS); - } - } // else didn't exist and still doesn't - - /* fire onStop() */ - for (final AdvancedFileAlterationListener listener : listeners) { - listener.onStop(this); - } - } - - /** - * Compare two file lists for files which have been created, modified or deleted. - * - * @param parent The parent entry - * @param previous The original list of files - * @param files The current list of files - */ - private void checkAndNotify(final FileEntry parent, final FileEntry[] previous, final File[] files, int delay) { - if (files != null && files.length > 0) { - int c = 0; - final FileEntry[] current = files.length > 0 ? new FileEntry[files.length] : EMPTY_ENTRIES; - for (final FileEntry entry : previous) { - while (c < files.length && comparator.compare(entry.getFile(), files[c]) > 0) { - current[c] = createFileEntry(parent, files[c]); - doCreate(current[c], delay); - c++; - } - if (c < files.length && comparator.compare(entry.getFile(), files[c]) == 0) { - doMatch(entry, files[c], delay); - checkAndNotify(entry, entry.getChildren(), listFiles(files[c]), delay); - current[c] = entry; - c++; - } else { - checkAndNotify(entry, entry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, delay); - doDelete(entry); - } - } - for (; c < files.length; c++) { - current[c] = createFileEntry(parent, files[c]); - doCreate(current[c], delay); - } - parent.setChildren(current); - } - } - - /** - * Create a new file entry for the specified file. - * - * @param parent The parent file entry - * @param file The file to create an entry for - * @return A new file entry - */ - private FileEntry createFileEntry(final FileEntry parent, final File file) { - final FileEntry entry = parent.newChildInstance(file); - entry.refresh(file); - final FileEntry[] children = doListFiles(file, entry); - entry.setChildren(children); - return entry; - } - - /** - * List the files - * - * @param file The file to list files for - * @param entry the parent entry - * @return The child files - */ - private FileEntry[] doListFiles(File file, FileEntry entry) { - final File[] files = listFiles(file); - final FileEntry[] children = files.length > 0 ? new FileEntry[files.length] : EMPTY_ENTRIES; - for (int i = 0; i < files.length; i++) { - children[i] = createFileEntry(entry, files[i]); - } - return children; - } - - /** - * Fire directory/file created events to the registered listeners. - * - * @param entry The file entry - */ - private void doCreate(final FileEntry entry, int delay) { - for (final AdvancedFileAlterationListener listener : listeners) { - if (entry.isDirectory()) { - listener.onDirectoryCreate(entry.getFile()); - } else { - listener.onFileCreate(entry.getFile(), delay); - } - } - final FileEntry[] children = entry.getChildren(); - for (final FileEntry aChildren : children) { - doCreate(aChildren, delay); - } - } - - /** - * Fire directory/file change events to the registered listeners. - * - * @param entry The previous file system entry - * @param file The current file - */ - private void doMatch(final FileEntry entry, final File file, int delay) { - if (entry.refresh(file)) { - for (final AdvancedFileAlterationListener listener : listeners) { - if (entry.isDirectory()) { - listener.onDirectoryChange(file); - } else { - listener.onFileChange(file, delay); - } - } - } - } - - /** - * Fire directory/file delete events to the registered listeners. - * - * @param entry The file entry - */ - private void doDelete(final FileEntry entry) { - for (final AdvancedFileAlterationListener listener : listeners) { - if (entry.isDirectory()) { - listener.onDirectoryDelete(entry.getFile()); - } else { - listener.onFileDelete(entry.getFile()); - } - } - } - - /** - * List the contents of a directory - * - * @param file The file to list the contents of - * @return the directory contents or a zero length array if - * the empty or the file is not a directory - */ - private File[] listFiles(final File file) { - File[] children = null; - if (file.isDirectory()) { - children = fileFilter == null ? file.listFiles() : file.listFiles(fileFilter); - } - if (children == null) { - children = FileUtils.EMPTY_FILE_ARRAY; - } - if (comparator != null && children.length > 1) { - Arrays.sort(children, comparator); - } - return children; - } - - /** - * Provide a String representation of this observer. - * - * @return a String representation of this observer - */ - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getClass().getSimpleName()); - builder.append("[file='"); - builder.append(getDirectory().getPath()); - builder.append('\''); - if (fileFilter != null) { - builder.append(", "); - builder.append(fileFilter.toString()); - } - builder.append(", listeners="); - builder.append(listeners.size()); - builder.append("]"); - return builder.toString(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/main/java/com/owncloud/android/services/observer/SyncedFolderObserverService.java deleted file mode 100644 index a6bd52821820..000000000000 --- a/src/main/java/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Nextcloud Android client application - * - * @author Tobias Kaminsky - * @author Andy Scherzinger - * @author Mario Danic - * Copyright (C) 2016 Tobias Kaminsky, Andy Scherzinger - * Copyright (C) 2017 Mario Danic - *

- * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - *

- * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - *

- * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.owncloud.android.services.observer; - -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.IBinder; - -import com.owncloud.android.MainApp; -import com.owncloud.android.R; -import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.datamodel.SyncedFolderProvider; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.services.AdvancedFileAlterationListener; - -import org.apache.commons.io.monitor.FileAlterationMonitor; -import org.apache.commons.io.monitor.FileAlterationObserver; - -import java.io.File; -import java.io.FileFilter; - -public class SyncedFolderObserverService extends Service { - private static final String TAG = "SyncedFolderObserverService"; - - private static final int MONITOR_SCAN_INTERVAL = 1000; - - private final IBinder mBinder = new SyncedFolderObserverBinder(); - private FileAlterationMonitor monitor; - private FileFilter fileFilter; - - @Override - public void onCreate() { - SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(MainApp.getAppContext(). - getContentResolver()); - monitor = new FileAlterationMonitor(MONITOR_SCAN_INTERVAL); - - fileFilter = new FileFilter() { - @Override - public boolean accept(File pathname) { - return !pathname.getName().startsWith(".") && !pathname.getName().endsWith(".tmp") && - !pathname.getName().endsWith(".temp") && !pathname.getName().endsWith(".thumbnail"); - } - }; - - - for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { - if (syncedFolder.isEnabled()) { - AdvancedFileAlterationObserver observer = new AdvancedFileAlterationObserver(syncedFolder, fileFilter); - - try { - observer.init(); - observer.addListener(new AdvancedFileAlterationListener(syncedFolder, - getResources().getBoolean(R.bool.syncedFolder_light))); - monitor.addObserver(observer); - } catch (Exception e) { - Log_OC.d(TAG, "Failed getting an observer to initialize " + e); - } - - } - } - - - try { - monitor.start(); - } catch (Exception e) { - Log_OC.d(TAG, "Something went very wrong at onStartCommand"); - } - - } - - @Override - public void onDestroy() { - - super.onDestroy(); - for (FileAlterationObserver fileAlterationObserver : monitor.getObservers()) { - AdvancedFileAlterationObserver advancedFileAlterationObserver = (AdvancedFileAlterationObserver) - fileAlterationObserver; - try { - monitor.removeObserver(advancedFileAlterationObserver); - advancedFileAlterationObserver.checkAndNotifyNow(); - advancedFileAlterationObserver.destroy(); - } catch (Exception e) { - Log_OC.d(TAG, "Something went very wrong on trying to destroy observers"); - } - } - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - return Service.START_NOT_STICKY; - } - - /** - * Restart oberver if it is enabled - * If syncedFolder exists already, use it, otherwise create new observer - * - * @param syncedFolder - */ - - public void restartObserver(SyncedFolder syncedFolder) { - boolean found = false; - AdvancedFileAlterationObserver advancedFileAlterationObserver; - for (FileAlterationObserver fileAlterationObserver : monitor.getObservers()) { - advancedFileAlterationObserver = - (AdvancedFileAlterationObserver) fileAlterationObserver; - if (advancedFileAlterationObserver.getSyncedFolderID() == syncedFolder.getId()) { - monitor.removeObserver(fileAlterationObserver); - advancedFileAlterationObserver.checkAndNotifyNow(); - try { - advancedFileAlterationObserver.destroy(); - } catch (Exception e) { - Log_OC.d(TAG, "Failed to destroy the observer in restart"); - } - - if (syncedFolder.isEnabled()) { - try { - advancedFileAlterationObserver = new AdvancedFileAlterationObserver(syncedFolder, fileFilter); - advancedFileAlterationObserver.init(); - advancedFileAlterationObserver.addListener(new AdvancedFileAlterationListener(syncedFolder, - getResources().getBoolean(R.bool.syncedFolder_light))); - monitor.addObserver(advancedFileAlterationObserver); - } catch (Exception e) { - Log_OC.d(TAG, "Failed getting an observer to initialize"); - } - } else { - monitor.removeObserver(fileAlterationObserver); - } - found = true; - break; - } - } - - if (!found && syncedFolder.isEnabled()) { - try { - advancedFileAlterationObserver = new AdvancedFileAlterationObserver(syncedFolder, fileFilter); - advancedFileAlterationObserver.init(); - advancedFileAlterationObserver.addListener(new AdvancedFileAlterationListener(syncedFolder, - getResources().getBoolean(R.bool.syncedFolder_light))); - monitor.addObserver(advancedFileAlterationObserver); - } catch (Exception e) { - Log_OC.d(TAG, "Failed getting an observer to initialize"); - } - - } - - } - - @Override - public IBinder onBind(Intent arg0) { - return mBinder; - } - - public class SyncedFolderObserverBinder extends Binder { - public SyncedFolderObserverService getService() { - return SyncedFolderObserverService.this; - } - } - -} diff --git a/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java b/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java index 01ebfd8a8f12..3a52a18124f6 100644 --- a/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java @@ -37,7 +37,7 @@ import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.services.ContactsBackupJob; +import com.owncloud.android.jobs.ContactsBackupJob; import com.owncloud.android.ui.fragment.FileFragment; import com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment; import com.owncloud.android.ui.fragment.contactsbackup.ContactsBackupFragment; diff --git a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 2391ad12cdbb..4042fb0eb0c3 100644 --- a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -211,12 +211,6 @@ protected void setupDrawer() { setupDrawerMenu(mNavigationView); setupQuotaElement(); - - // show folder sync menu item only for Android 6+ - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M && - mNavigationView.getMenu().findItem(R.id.nav_folder_sync) != null) { - mNavigationView.getMenu().removeItem(R.id.nav_folder_sync); - } } setupDrawerToggle(); @@ -366,7 +360,7 @@ public void run() { } if (getResources().getBoolean(R.bool.syncedFolder_light)) { - navigationView.getMenu().removeItem(R.id.nav_folder_sync); + navigationView.getMenu().removeItem(R.id.nav_synced_folders); } if (!getResources().getBoolean(R.bool.show_drawer_logout)) { @@ -450,9 +444,9 @@ private void selectNavigationItem(final MenuItem menuItem) { Intent notificationsIntent = new Intent(getApplicationContext(), NotificationsActivity.class); startActivity(notificationsIntent); break; - case R.id.nav_folder_sync: - Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class); - startActivity(folderSyncIntent); + case R.id.nav_synced_folders: + Intent syncedFoldersIntent = new Intent(getApplicationContext(), SyncedFoldersActivity.class); + startActivity(syncedFoldersIntent); break; case R.id.nav_contacts: Intent contactsIntent = new Intent(getApplicationContext(), ContactsPreferenceActivity.class); diff --git a/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java b/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java index ccdf152a1b12..ae2f6aacffa4 100644 --- a/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java @@ -21,7 +21,6 @@ package com.owncloud.android.ui.activity; -import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.widget.DrawerLayout; @@ -113,7 +112,6 @@ protected void onCreate(Bundle savedInstanceState) { webSettings.setJavaScriptEnabled(true); webSettings.setDomStorageEnabled(true); - final Activity activity = this; final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar); webview.setWebChromeClient(new WebChromeClient() { @@ -124,7 +122,8 @@ public void onProgressChanged(WebView view, int progress) { webview.setWebViewClient(new WebViewClient() { public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - webview.loadData(DisplayUtils.getData(getResources().openRawResource(R.raw.custom_error)),"text/html; charset=UTF-8", null); + webview.loadData(DisplayUtils.getData(getResources().openRawResource(R.raw.custom_error)), + "text/html; charset=UTF-8", null); } }); diff --git a/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index da2b9789f201..2eb942d5ceaf 100644 --- a/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -476,7 +476,7 @@ public void showLoadingDialog(String message) { Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG); if (frag == null) { Log_OC.d(TAG, "show loading dialog"); - LoadingDialog loading = new LoadingDialog(message); + LoadingDialog loading = LoadingDialog.newInstance(message); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); loading.show(ft, DIALOG_WAIT_TAG); diff --git a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index c543e795b77c..102058bc4235 100644 --- a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -282,9 +282,8 @@ public void onClick(View v) { */ private void upgradeNotificationForInstantUpload() { // check for Android 6+ if legacy instant upload is activated --> disable + show info - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - (PreferenceManager.instantPictureUploadEnabled(this) || - PreferenceManager.instantPictureUploadEnabled(this))) { + if (PreferenceManager.instantPictureUploadEnabled(this) || + PreferenceManager.instantPictureUploadEnabled(this)) { // remove legacy shared preferences SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); @@ -303,14 +302,14 @@ private void upgradeNotificationForInstantUpload() { // show info pop-up new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog) - .setTitle(R.string.drawer_folder_sync) - .setMessage(R.string.folder_sync_new_info) + .setTitle(R.string.drawer_synced_folders) + .setMessage(R.string.synced_folders_new_info) .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // show instant upload - Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class); + Intent syncedFoldersIntent = new Intent(getApplicationContext(), SyncedFoldersActivity.class); dialog.dismiss(); - startActivity(folderSyncIntent); + startActivity(syncedFoldersIntent); } }) .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() { @@ -318,7 +317,7 @@ public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) - .setIcon(R.drawable.nav_folder_sync) + .setIcon(R.drawable.nav_synced_folders) .show(); } } @@ -477,7 +476,7 @@ protected void onNewIntent(Intent intent) { super.onNewIntent(intent); if(intent.getAction()!=null && intent.getAction().equalsIgnoreCase(ACTION_DETAILS)){ setIntent(intent); - setFile((OCFile)intent.getParcelableExtra(EXTRA_FILE)); + setFile(intent.getParcelableExtra(EXTRA_FILE)); } } @@ -915,7 +914,9 @@ private void requestUploadOfFilesFromFileSystem(Intent data, int resultCode) { null, // MIME type will be detected from file name behaviour, false, // do not create parent folder if not existent - UploadFileOperation.CREATED_BY_USER + UploadFileOperation.CREATED_BY_USER, + false, + false ); } else { diff --git a/src/main/java/com/owncloud/android/ui/activity/FingerprintActivity.java b/src/main/java/com/owncloud/android/ui/activity/FingerprintActivity.java index 0acaeac3fbc2..290964b7050c 100644 --- a/src/main/java/com/owncloud/android/ui/activity/FingerprintActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/FingerprintActivity.java @@ -77,7 +77,6 @@ public class FingerprintActivity extends AppCompatActivity { public final static String KEY_CHECK_RESULT = "KEY_CHECK_RESULT"; - public final static String PREFERENCE_USE_FINGERPRINT = "use_fingerprint"; public static final String ANDROID_KEY_STORE = "AndroidKeyStore"; private KeyStore keyStore; @@ -286,7 +285,7 @@ static public boolean isFingerprintReady(Context context) { } } -@RequiresApi(Build.VERSION_CODES.M) +@RequiresApi(api = Build.VERSION_CODES.M) class FingerprintHandler extends FingerprintManager.AuthenticationCallback { private TextView text; diff --git a/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java b/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java index 719128a13b45..54bc8748675f 100644 --- a/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java @@ -302,6 +302,7 @@ public boolean onOptionsItemSelected(MenuItem item) { retval = super.onOptionsItemSelected(item); break; } + return retval; } diff --git a/src/main/java/com/owncloud/android/ui/activity/LogHistoryActivity.java b/src/main/java/com/owncloud/android/ui/activity/LogHistoryActivity.java index 6049e02757d7..6a2fd855a9b3 100644 --- a/src/main/java/com/owncloud/android/ui/activity/LogHistoryActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/LogHistoryActivity.java @@ -273,9 +273,7 @@ private String readLogFile() { */ public void showLoadingDialog() { // Construct dialog - LoadingDialog loading = new LoadingDialog( - getResources().getString(R.string.log_progress_dialog_text) - ); + LoadingDialog loading = LoadingDialog.newInstance(getResources().getString(R.string.log_progress_dialog_text)); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); loading.show(ft, DIALOG_WAIT_TAG); diff --git a/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java b/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java index a60af3c7974b..5f1b673420b5 100644 --- a/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java @@ -50,8 +50,7 @@ import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.services.AccountRemovalJob; -import com.owncloud.android.services.AutoUploadJob; +import com.owncloud.android.jobs.AccountRemovalJob; import com.owncloud.android.services.OperationsService; import com.owncloud.android.ui.adapter.AccountListAdapter; import com.owncloud.android.ui.adapter.AccountListItem; @@ -407,7 +406,7 @@ private void performAccountRemoval(Account account) { // store pending account removal ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver()); - arbitraryDataProvider.storeOrUpdateKeyValue(account, PENDING_FOR_REMOVAL, String.valueOf(true)); + arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PENDING_FOR_REMOVAL, String.valueOf(true)); // Cancel transfers if (mUploaderBinder != null) { @@ -419,7 +418,7 @@ private void performAccountRemoval(Account account) { // schedule job PersistableBundleCompat bundle = new PersistableBundleCompat(); - bundle.putString(AutoUploadJob.ACCOUNT, account.name); + bundle.putString(AccountRemovalJob.ACCOUNT, account.name); new JobRequest.Builder(AccountRemovalJob.TAG) .setExecutionWindow(1_000L, 10_000L) diff --git a/src/main/java/com/owncloud/android/ui/activity/Preferences.java b/src/main/java/com/owncloud/android/ui/activity/Preferences.java index e5c33bd9acfd..67651201a8dd 100644 --- a/src/main/java/com/owncloud/android/ui/activity/Preferences.java +++ b/src/main/java/com/owncloud/android/ui/activity/Preferences.java @@ -85,9 +85,11 @@ */ public class Preferences extends PreferenceActivity implements StorageMigration.StorageMigrationProgressListener { - + private static final String TAG = Preferences.class.getSimpleName(); + public final static String PREFERENCE_USE_FINGERPRINT = "use_fingerprint"; + private static final String SCREEN_NAME = "Settings"; private static final int ACTION_SELECT_UPLOAD_PATH = 1; @@ -107,6 +109,7 @@ public class Preferences extends PreferenceActivity private SwitchPreference pCode; private SwitchPreference fPrint; private SwitchPreference mShowHiddenFiles; + private SwitchPreference mExpertMode; private Preference pAboutApp; private AppCompatDelegate mDelegate; @@ -190,13 +193,14 @@ public void onCreate(Bundle savedInstanceState) { accentColor)); // Synced folders - PreferenceCategory preferenceCategoryFolderSync = (PreferenceCategory) findPreference("folder_sync"); - preferenceCategoryFolderSync.setTitle(ThemeUtils.getColoredTitle(getString(R.string.drawer_folder_sync), + PreferenceCategory preferenceCategorySyncedFolders = + (PreferenceCategory) findPreference("synced_folders_category"); + preferenceCategorySyncedFolders.setTitle(ThemeUtils.getColoredTitle(getString(R.string.drawer_synced_folders), accentColor)); PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference("preference_screen"); if (!getResources().getBoolean(R.bool.syncedFolder_light)) { - preferenceScreen.removePreference(preferenceCategoryFolderSync); + preferenceScreen.removePreference(preferenceCategorySyncedFolders); } else { // Upload on WiFi final ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver()); @@ -209,28 +213,28 @@ public void onCreate(Bundle savedInstanceState) { pUploadOnWifiCheckbox.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - arbitraryDataProvider.storeOrUpdateKeyValue(account, SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI, + arbitraryDataProvider.storeOrUpdateKeyValue(account.name, SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI, String.valueOf(pUploadOnWifiCheckbox.isChecked())); return true; } }); - Preference pSyncedFolder = findPreference("folder_sync_folders"); + Preference pSyncedFolder = findPreference("synced_folders_configure_folders"); if (pSyncedFolder != null) { if (getResources().getBoolean(R.bool.syncedFolder_light) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { pSyncedFolder.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class); - folderSyncIntent.putExtra(FolderSyncActivity.EXTRA_SHOW_SIDEBAR, false); - startActivity(folderSyncIntent); + Intent syncedFoldersIntent = new Intent(getApplicationContext(), SyncedFoldersActivity.class); + syncedFoldersIntent.putExtra(SyncedFoldersActivity.EXTRA_SHOW_SIDEBAR, false); + startActivity(syncedFoldersIntent); return true; } }); } else { - preferenceCategoryFolderSync.removePreference(pSyncedFolder); + preferenceCategorySyncedFolders.removePreference(pSyncedFolder); } } } @@ -265,7 +269,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } boolean fPrintEnabled = getResources().getBoolean(R.bool.fingerprint_enabled); - fPrint = (SwitchPreference) findPreference(FingerprintActivity.PREFERENCE_USE_FINGERPRINT); + fPrint = (SwitchPreference) findPreference(PREFERENCE_USE_FINGERPRINT); if (fPrint != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (FingerprintActivity.isFingerprintCapable(MainApp.getAppContext()) && fPrintEnabled) { @@ -332,9 +336,21 @@ public boolean onPreferenceClick(Preference preference) { }); } else { preferenceCategoryDetails.removePreference(mShowHiddenFiles); - } + mExpertMode = (SwitchPreference) findPreference("expert_mode"); + mExpertMode.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + SharedPreferences appPrefs = + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor editor = appPrefs.edit(); + editor.putBoolean("expert_mode", mExpertMode.isChecked()); + editor.apply(); + return true; + } + }); + PreferenceCategory preferenceCategoryMore = (PreferenceCategory) findPreference("more"); preferenceCategoryMore.setTitle(ThemeUtils.getColoredTitle(getString(R.string.prefs_category_more), accentColor)); @@ -468,7 +484,11 @@ public boolean onPreferenceClick(Preference preference) { } } - boolean loggerEnabled = getResources().getBoolean(R.bool.logger_enabled) || BuildConfig.DEBUG; + SharedPreferences appPrefs = + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + + boolean loggerEnabled = getResources().getBoolean(R.bool.logger_enabled) || BuildConfig.DEBUG || + appPrefs.getBoolean("expert_mode", false); Preference pLogger = findPreference("logger"); if (pLogger != null) { if (loggerEnabled) { @@ -521,12 +541,12 @@ public boolean onPreferenceClick(Preference preference) { mPrefStoragePath.setEntryValues(values); mPrefStoragePath.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - String newPath = (String) newValue; - if (mStoragePath.equals(newPath)) { - return true; - } + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String newPath = (String) newValue; + if (mStoragePath.equals(newPath)) { + return true; + } StorageMigration storageMigration = new StorageMigration(Preferences.this, mStoragePath, newPath); @@ -540,89 +560,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category"); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - // Instant upload via preferences on pre Android Marshmallow - mPrefInstantUploadPath = findPreference("instant_upload_path"); - if (mPrefInstantUploadPath != null) { - - mPrefInstantUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - if (!mUploadPath.endsWith(OCFile.PATH_SEPARATOR)) { - mUploadPath += OCFile.PATH_SEPARATOR; - } - Intent intent = new Intent(Preferences.this, UploadPathActivity.class); - intent.putExtra(UploadPathActivity.KEY_INSTANT_UPLOAD_PATH, mUploadPath); - startActivityForResult(intent, ACTION_SELECT_UPLOAD_PATH); - return true; - } - }); - } - - mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category"); - - mPrefInstantUploadUseSubfolders = findPreference("instant_upload_path_use_subfolders"); - mPrefInstantUploadPathWiFi = findPreference("instant_upload_on_wifi"); - mPrefInstantPictureUploadOnlyOnCharging = findPreference("instant_upload_on_charging"); - mPrefInstantUpload = (CheckBoxPreferenceWithLongTitle) findPreference("instant_uploading"); - - toggleInstantPictureOptions(mPrefInstantUpload.isChecked()); - - mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - toggleInstantPictureOptions((Boolean) newValue); - toggleInstantUploadBehaviour(mPrefInstantVideoUpload.isChecked(), (Boolean) newValue); - return true; - } - }); - - mPrefInstantVideoUploadPath = findPreference(PreferenceKeys.INSTANT_VIDEO_UPLOAD_PATH); - if (mPrefInstantVideoUploadPath != null) { - - mPrefInstantVideoUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - if (!mUploadVideoPath.endsWith(OCFile.PATH_SEPARATOR)) { - mUploadVideoPath += OCFile.PATH_SEPARATOR; - } - Intent intent = new Intent(Preferences.this, UploadPathActivity.class); - intent.putExtra(UploadPathActivity.KEY_INSTANT_UPLOAD_PATH, - mUploadVideoPath); - startActivityForResult(intent, ACTION_SELECT_UPLOAD_VIDEO_PATH); - return true; - } - }); - } - - mPrefInstantVideoUploadUseSubfolders = findPreference("instant_video_upload_path_use_subfolders"); - mPrefInstantVideoUploadPathWiFi = findPreference("instant_video_upload_on_wifi"); - mPrefInstantVideoUpload = (CheckBoxPreferenceWithLongTitle) findPreference("instant_video_uploading"); - mPrefInstantVideoUploadOnlyOnCharging = findPreference("instant_video_upload_on_charging"); - toggleInstantVideoOptions(mPrefInstantVideoUpload.isChecked()); - mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - toggleInstantVideoOptions((Boolean) newValue); - toggleInstantUploadBehaviour( - (Boolean) newValue, - mPrefInstantUpload.isChecked()); - return true; - } - }); - - mPrefInstantUploadBehaviour = findPreference("prefs_instant_behaviour"); - toggleInstantUploadBehaviour( - mPrefInstantVideoUpload.isChecked(), - mPrefInstantUpload.isChecked()); - - loadInstantUploadPath(); - loadInstantUploadVideoPath(); - } else { - // Instant upload is handled via synced folders on Android Lollipop and up - getPreferenceScreen().removePreference(mPrefInstantUploadCategory); - } + getPreferenceScreen().removePreference(mPrefInstantUploadCategory); // About category PreferenceCategory preferenceCategoryAbout = (PreferenceCategory) findPreference("about"); @@ -716,48 +654,12 @@ public void run() { mUri = OwnCloudClientManagerFactory.getDefaultSingleton(). getClientFor(ocAccount, getApplicationContext()).getBaseUri(); } catch (Throwable t) { - Log_OC.e(TAG,"Error retrieving user's base URI", t); + Log_OC.e(TAG, "Error retrieving user's base URI", t); } } }); t.start(); } - - private void toggleInstantPictureOptions(Boolean value){ - if (value) { - mPrefInstantUploadCategory.addPreference(mPrefInstantUploadPathWiFi); - mPrefInstantUploadCategory.addPreference(mPrefInstantUploadPath); - mPrefInstantUploadCategory.addPreference(mPrefInstantUploadUseSubfolders); - mPrefInstantUploadCategory.addPreference(mPrefInstantPictureUploadOnlyOnCharging); - } else { - mPrefInstantUploadCategory.removePreference(mPrefInstantUploadPathWiFi); - mPrefInstantUploadCategory.removePreference(mPrefInstantUploadPath); - mPrefInstantUploadCategory.removePreference(mPrefInstantUploadUseSubfolders); - mPrefInstantUploadCategory.removePreference(mPrefInstantPictureUploadOnlyOnCharging); - } - } - - private void toggleInstantVideoOptions(Boolean value){ - if (value) { - mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPathWiFi); - mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPath); - mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadUseSubfolders); - mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadOnlyOnCharging); - } else { - mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadPathWiFi); - mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadPath); - mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadUseSubfolders); - mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadOnlyOnCharging); - } - } - - private void toggleInstantUploadBehaviour(Boolean video, Boolean picture){ - if (picture || video) { - mPrefInstantUploadCategory.addPreference(mPrefInstantUploadBehaviour); - } else { - mPrefInstantUploadCategory.removePreference(mPrefInstantUploadBehaviour); - } - } @Override protected void onResume() { @@ -782,14 +684,14 @@ public boolean onMenuItemSelected(int featureId, MenuItem item) { Intent intent; switch (item.getItemId()) { - case android.R.id.home: - intent = new Intent(getBaseContext(), FileDisplayActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - break; - default: - Log_OC.w(TAG, "Unknown menu item triggered"); - return false; + case android.R.id.home: + intent = new Intent(getBaseContext(), FileDisplayActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + break; + default: + Log_OC.w(TAG, "Unknown menu item triggered"); + return false; } return true; } @@ -800,7 +702,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == ACTION_SELECT_UPLOAD_PATH && resultCode == RESULT_OK) { - OCFile folderToUpload = data.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER); + OCFile folderToUpload = data.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER); mUploadPath = folderToUpload.getRemotePath(); @@ -830,7 +732,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { .getDefaultSharedPreferences(getApplicationContext()).edit(); for (int i = 1; i <= 4; ++i) { - appPrefs.putString(PassCodeActivity.PREFERENCE_PASSCODE_D + i, passcode.substring(i-1, i)); + appPrefs.putString(PassCodeActivity.PREFERENCE_PASSCODE_D + i, passcode.substring(i - 1, i)); } appPrefs.putBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, true); appPrefs.apply(); @@ -868,10 +770,12 @@ public MenuInflater getMenuInflater() { public void setContentView(@LayoutRes int layoutResID) { getDelegate().setContentView(layoutResID); } + @Override public void setContentView(View view) { getDelegate().setContentView(view); } + @Override public void setContentView(View view, ViewGroup.LayoutParams params) { getDelegate().setContentView(view, params); @@ -994,7 +898,7 @@ private void loadStoragePath() { SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); mStoragePath = appPrefs.getString(PreferenceKeys.STORAGE_PATH, Environment.getExternalStorageDirectory() - .getAbsolutePath()); + .getAbsolutePath()); String storageDescription = DataStorageProvider.getInstance().getStorageDescriptionByPath(mStoragePath); mPrefStoragePath.setSummary(storageDescription); } diff --git a/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java b/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java index 7bf587335004..65947f2fccef 100755 --- a/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java @@ -913,7 +913,9 @@ public void uploadFile(String tmpname, String filename) { FileUploader.LOCAL_BEHAVIOUR_COPY, null, true, - UploadFileOperation.CREATED_BY_USER + UploadFileOperation.CREATED_BY_USER, + false, + false ); finish(); } diff --git a/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java b/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java index 933b1e057fb9..ab6770c0f6dd 100644 --- a/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java @@ -55,11 +55,9 @@ import java.util.ArrayList; /** - * Activity for sharing files + * Activity for sharing files. */ - -public class ShareActivity extends FileActivity - implements ShareFragmentListener { +public class ShareActivity extends FileActivity implements ShareFragmentListener { private static final String TAG = ShareActivity.class.getSimpleName(); @@ -70,8 +68,6 @@ public class ShareActivity extends FileActivity /// Tags for dialog fragments private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG"; - private static final String FTAG_SHARE_PASSWORD_DIALOG = "SHARE_PASSWORD_DIALOG"; - @Override protected void onCreate(Bundle savedInstanceState) { @@ -87,7 +83,6 @@ protected void onCreate(Bundle savedInstanceState) { ft.replace(R.id.share_fragment_container, fragment, TAG_SHARE_FRAGMENT); ft.commit(); } - } protected void onAccountSet(boolean stateWasRecovered) { @@ -144,10 +139,9 @@ private void doShareWith(String shareeName, String dataAuthority) { ); } - private int getAppropiatePermissions(ShareType shareType) { - // check if the Share is FERERATED + // check if the Share is FEDERATED boolean isFederated = ShareType.FEDERATED.equals(shareType); if (getFile().isSharedWithMe()) { @@ -245,9 +239,8 @@ && getEditShareFragment() != null && getEditShareFragment().isAdded()) { } - /** - * Updates the view, reading data from {@link com.owncloud.android.datamodel.FileDataStorageManager} + * Updates the view, reading data from {@link com.owncloud.android.datamodel.FileDataStorageManager}. */ private void refreshSharesFromStorageManager() { @@ -270,7 +263,6 @@ private void refreshSharesFromStorageManager() { editShareFragment.isAdded()) { editShareFragment.refreshUiFromDB(); } - } /** @@ -300,7 +292,6 @@ private EditShareFragment getEditShareFragment() { return (EditShareFragment) getSupportFragmentManager().findFragmentByTag(TAG_EDIT_SHARE_FRAGMENT); } - private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation, RemoteOperationResult result) { if (result.isSuccess()) { @@ -369,8 +360,5 @@ private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation ope t.show(); } } - } - - } diff --git a/src/main/java/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java similarity index 63% rename from src/main/java/com/owncloud/android/ui/activity/FolderSyncActivity.java rename to src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java index c2fcb5d347e6..47c22b68a213 100644 --- a/src/main/java/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java @@ -23,56 +23,66 @@ import android.accounts.Account; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; -import android.os.Handler; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; import android.support.design.widget.BottomNavigationView; +import android.support.design.widget.CollapsingToolbarLayout; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.MediaProvider; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.SyncedFolder; import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.ui.adapter.FolderSyncAdapter; +import com.owncloud.android.ui.adapter.SyncedFolderAdapter; import com.owncloud.android.ui.decoration.MediaGridItemDecoration; import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment; import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; import com.owncloud.android.utils.AnalyticsUtils; import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.FilesSyncHelper; import com.owncloud.android.utils.PermissionUtil; import com.owncloud.android.utils.ThemeUtils; import java.io.File; +import java.io.FileFilter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TimerTask; +import static android.support.design.widget.AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS; import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID; /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener, +public class SyncedFoldersActivity extends FileActivity implements SyncedFolderAdapter.ClickListener, SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener { private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG"; @@ -81,16 +91,16 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte private static final String SCREEN_NAME = "Auto upload"; - private static final String TAG = FolderSyncActivity.class.getSimpleName(); + private static final String TAG = SyncedFoldersActivity.class.getSimpleName(); private RecyclerView mRecyclerView; - private FolderSyncAdapter mAdapter; + private SyncedFolderAdapter mAdapter; private LinearLayout mProgress; private TextView mEmpty; private SyncedFolderProvider mSyncedFolderProvider; - private List syncFolderItems; private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment; private boolean showSidebar = true; + private RelativeLayout mCustomFolderRelativeLayout; @Override protected void onCreate(Bundle savedInstanceState) { @@ -100,13 +110,37 @@ protected void onCreate(Bundle savedInstanceState) { showSidebar = getIntent().getExtras().getBoolean(EXTRA_SHOW_SIDEBAR); } - setContentView(R.layout.folder_sync_layout); + setContentView(R.layout.synced_folders_layout); // setup toolbar setupToolbar(); + CollapsingToolbarLayout mCollapsingToolbarLayout = ((CollapsingToolbarLayout) + findViewById(R.id.collapsing_toolbar)); + mCollapsingToolbarLayout.setTitle(this.getString(R.string.drawer_synced_folders)); + + mCustomFolderRelativeLayout = (RelativeLayout) findViewById(R.id.custom_folder_toolbar); + + SharedPreferences appPrefs = + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + + + findViewById(R.id.toolbar).post(() -> { + if (!appPrefs.getBoolean("expert_mode", false)) { + findViewById(R.id.app_bar).getLayoutParams().height = findViewById(R.id.toolbar).getHeight(); + + AppBarLayout.LayoutParams p = (AppBarLayout.LayoutParams) mCollapsingToolbarLayout.getLayoutParams(); + p.setScrollFlags(SCROLL_FLAG_ENTER_ALWAYS); + mCollapsingToolbarLayout.setLayoutParams(p); + mCustomFolderRelativeLayout.setVisibility(View.GONE); + } else { + mCustomFolderRelativeLayout.setVisibility(View.VISIBLE); + findViewById(R.id.app_bar).setBackgroundColor(getResources().getColor(R.color.filelist_icon_backgorund)); + } + }); + // setup drawer - setupDrawer(R.id.nav_folder_sync); + setupDrawer(R.id.nav_synced_folders); if (!showSidebar) { setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); @@ -117,7 +151,7 @@ protected void onCreate(Bundle savedInstanceState) { ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { - ThemeUtils.setColoredTitle(getSupportActionBar(), getString(R.string.drawer_folder_sync)); + ThemeUtils.setColoredTitle(getSupportActionBar(), getString(R.string.drawer_synced_folders)); actionBar.setDisplayHomeAsUpEnabled(true); } @@ -143,7 +177,7 @@ private void setupContent() { final int gridWidth = getResources().getInteger(R.integer.media_grid_width); boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light); - mAdapter = new FolderSyncAdapter(this, gridWidth, this, lightVersion); + mAdapter = new SyncedFolderAdapter(this, gridWidth, this, lightVersion); mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver()); final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); @@ -173,33 +207,25 @@ private void load(final int perFolderMediaItemLimit, boolean force) { return; } setListShown(false); - final Handler mHandler = new Handler(); - new Thread(new Runnable() { - @Override - public void run() { - final List mediaFolders = MediaProvider.getMediaFolders(getContentResolver(), - perFolderMediaItemLimit, FolderSyncActivity.this); - List syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders(); - List currentAccountSyncedFoldersList = new ArrayList<>(); - Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(FolderSyncActivity.this); - for (SyncedFolder syncedFolder : syncedFolderArrayList) { - if (syncedFolder.getAccount().equals(currentAccount.name)) { - currentAccountSyncedFoldersList.add(syncedFolder); - } - } + final List mediaFolders = MediaProvider.getImageFolders(getContentResolver(), + perFolderMediaItemLimit, SyncedFoldersActivity.this); + mediaFolders.addAll(MediaProvider.getVideoFolders(getContentResolver(), perFolderMediaItemLimit)); + + List syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders(); + List currentAccountSyncedFoldersList = new ArrayList<>(); + Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(SyncedFoldersActivity.this); + for (SyncedFolder syncedFolder : syncedFolderArrayList) { + if (syncedFolder.getAccount().equals(currentAccount.name)) { + currentAccountSyncedFoldersList.add(syncedFolder); + } + } - syncFolderItems = sortSyncedFolderItems(mergeFolderData(currentAccountSyncedFoldersList, - mediaFolders)); + List syncFolderItems = sortSyncedFolderItems( + mergeFolderData(currentAccountSyncedFoldersList, mediaFolders)); - mHandler.post(new TimerTask() { - @Override - public void run() { - mAdapter.setSyncFolderItems(syncFolderItems); - setListShown(true); - } - }); - } - }).start(); + mAdapter.setSyncFolderItems(syncFolderItems); + mAdapter.notifyDataSetChanged(); + setListShown(true); } /** @@ -215,20 +241,23 @@ private List mergeFolderData(List syncedF Map syncedFoldersMap = createSyncedFoldersMap(syncedFolders); List result = new ArrayList<>(); - for (MediaFolder mediaFolder : mediaFolders) { - if (syncedFoldersMap.containsKey(mediaFolder.absolutePath)) { - SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath); - syncedFoldersMap.remove(mediaFolder.absolutePath); - result.add(createSyncedFolder(syncedFolder, mediaFolder)); + if (syncedFoldersMap.containsKey(mediaFolder.absolutePath+"-"+mediaFolder.type)) { + SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath+"-"+mediaFolder.type); + syncedFoldersMap.remove(mediaFolder.absolutePath+"-"+mediaFolder.type); + + if (MediaFolderType.CUSTOM == syncedFolder.getType()) { + result.add(createSyncedFolderWithoutMediaFolder(syncedFolder)); + } else { + result.add(createSyncedFolder(syncedFolder, mediaFolder)); + } } else { result.add(createSyncedFolderFromMediaFolder(mediaFolder)); } } for (SyncedFolder syncedFolder : syncedFoldersMap.values()) { - SyncedFolderDisplayItem syncedFolderDisplayItem = createSyncedFolderWithoutMediaFolder(syncedFolder); - result.add(syncedFolderDisplayItem); + result.add(createSyncedFolderWithoutMediaFolder(syncedFolder)); } return result; @@ -277,6 +306,11 @@ public int compare(SyncedFolderDisplayItem f1, SyncedFolderDisplayItem f2) { @NonNull private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) { + + File localFolder = new File(syncedFolder.getLocalPath()); + File[] files = getFileList(localFolder); + List filePaths = getDisplayFilePathList(files); + return new SyncedFolderDisplayItem( syncedFolder.getId(), syncedFolder.getLocalPath(), @@ -287,7 +321,10 @@ private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull Sy syncedFolder.getAccount(), syncedFolder.getUploadAction(), syncedFolder.isEnabled(), - new File(syncedFolder.getLocalPath()).getName()); + filePaths, + localFolder.getName(), + files.length, + syncedFolder.getType()); } /** @@ -311,7 +348,8 @@ private SyncedFolderDisplayItem createSyncedFolder(@NonNull SyncedFolder syncedF syncedFolder.isEnabled(), mediaFolder.filePaths, mediaFolder.folderName, - mediaFolder.numberOfFiles); + mediaFolder.numberOfFiles, + mediaFolder.type); } /** @@ -334,7 +372,42 @@ private SyncedFolderDisplayItem createSyncedFolderFromMediaFolder(@NonNull Media false, mediaFolder.filePaths, mediaFolder.folderName, - mediaFolder.numberOfFiles); + mediaFolder.numberOfFiles, + mediaFolder.type); + } + + private File[] getFileList(File localFolder) { + File[] files = localFolder.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return !pathname.isDirectory(); + } + }); + + if (files != null) { + Arrays.sort(files, new Comparator() { + public int compare(File f1, File f2) { + return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified()); + } + }); + } else { + files = new File[]{}; + } + + return files; + } + + private List getDisplayFilePathList(File[] files) { + List filePaths = null; + + if (files != null && files.length > 0) { + filePaths = new ArrayList<>(); + for (int i = 0; i < 7 && i < files.length; i++) { + filePaths.add(files[i].getAbsolutePath()); + } + } + + return filePaths; } /** @@ -348,7 +421,7 @@ private Map createSyncedFoldersMap(List sync Map result = new HashMap<>(); if (syncFolders != null) { for (SyncedFolder syncFolder : syncFolders) { - result.put(syncFolder.getLocalPath(), syncFolder); + result.put(syncFolder.getLocalPath()+"-"+syncFolder.getType(), syncFolder); } } return result; @@ -389,6 +462,7 @@ public boolean onOptionsItemSelected(MenuItem item) { result = super.onOptionsItemSelected(item); break; } + return result; } @@ -409,15 +483,27 @@ public void showFiles(boolean onDeviceOnly) { @Override public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) { + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext(). + getContentResolver()); + if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) { - mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderDisplayItem.getId(), + mSyncedFolderProvider.updateSyncedFolderEnabled(syncedFolderDisplayItem.getId(), syncedFolderDisplayItem.isEnabled()); } else { - long storedId = mSyncedFolderProvider.storeFolderSync(syncedFolderDisplayItem); + long storedId = mSyncedFolderProvider.storeSyncedFolder(syncedFolderDisplayItem); if (storedId != -1) { syncedFolderDisplayItem.setId(storedId); } } + + if (syncedFolderDisplayItem.isEnabled()) { + FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem); + } else { + String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolderDisplayItem.getId(); + arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey); + } + FilesSyncHelper.scheduleNJobs(false); + } @Override @@ -437,36 +523,76 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) { OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER); mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath()); - - } else { + } if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_LOCAL_FOLDER + && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) { + String localPath = data.getStringExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES); + mSyncedFolderPreferencesDialogFragment.setLocalFolderSummary(localPath); + } + else { super.onActivityResult(requestCode, resultCode, data); } } @Override public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { - SyncedFolderDisplayItem item = syncFolderItems.get(syncedFolder.getSection()); - boolean dirty = item.isEnabled() != syncedFolder.getEnabled(); - item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder - .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder - .getUploadAction(), syncedFolder.getEnabled()); - - if (syncedFolder.getId() == UNPERSISTED_ID) { - // newly set up folder sync config - long storedId = mSyncedFolderProvider.storeFolderSync(item); + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext(). + getContentResolver()); + + // custom folders newly created aren't in the list already, + // so triggering a refresh + if (MediaFolderType.CUSTOM.equals(syncedFolder.getType()) && syncedFolder.getId() == UNPERSISTED_ID) { + SyncedFolderDisplayItem newCustomFolder = new SyncedFolderDisplayItem( + SyncedFolder.UNPERSISTED_ID, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), + syncedFolder.getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), + syncedFolder.getAccount(), syncedFolder.getUploadAction(), syncedFolder.getEnabled(), + new File(syncedFolder.getLocalPath()).getName(), syncedFolder.getType()); + long storedId = mSyncedFolderProvider.storeSyncedFolder(newCustomFolder); if (storedId != -1) { - item.setId(storedId); + newCustomFolder.setId(storedId); + if (newCustomFolder.isEnabled()) { + FilesSyncHelper.insertAllDBEntriesForSyncedFolder(newCustomFolder); + } else { + String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + newCustomFolder.getId(); + arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey); + } + FilesSyncHelper.scheduleNJobs(false); } - + mAdapter.addSyncFolderItem(newCustomFolder); } else { - // existing synced folder setup to be updated - mSyncedFolderProvider.updateSyncFolder(item); - } - mSyncedFolderPreferencesDialogFragment = null; + SyncedFolderDisplayItem item = mAdapter.get(syncedFolder.getSection()); + item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder + .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder + .getUploadAction(), syncedFolder.getEnabled()); + + if (syncedFolder.getId() == UNPERSISTED_ID) { + // newly set up folder sync config + long storedId = mSyncedFolderProvider.storeSyncedFolder(item); + if (storedId != -1) { + item.setId(storedId); + if (item.isEnabled()) { + FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item); + } else { + String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId(); + arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey); + } + FilesSyncHelper.scheduleNJobs(false); + } + } else { + // existing synced folder setup to be updated + mSyncedFolderProvider.updateSyncFolder(item); + if (item.isEnabled()) { + FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item); + } else { + String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId(); + arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey); + } + FilesSyncHelper.scheduleNJobs(false); + } - if (dirty) { mAdapter.setSyncFolderItem(syncedFolder.getSection(), item); } + + mSyncedFolderPreferencesDialogFragment = null; } @Override @@ -474,6 +600,12 @@ public void onCancelSyncedFolderPreference() { mSyncedFolderPreferencesDialogFragment = null; } + @Override + public void onDeleteSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { + mSyncedFolderProvider.deleteSyncedFolder(syncedFolder.getId()); + mAdapter.removeItem(syncedFolder.getSection()); + } + /** * update given synced folder with the given values. * @@ -525,4 +657,13 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String permissi super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } + + public void onAddCustomFolderClick(View view) { + Log.d(TAG, "Show custom folder dialog"); + SyncedFolderDisplayItem emptyCustomFolder = new SyncedFolderDisplayItem( + SyncedFolder.UNPERSISTED_ID, null, null, true, false, + false, AccountUtils.getCurrentOwnCloudAccount(this).name, + FileUploader.LOCAL_BEHAVIOUR_FORGET, false, null, MediaFolderType.CUSTOM); + onSyncFolderSettingsClick(0, emptyCustomFolder); + } } diff --git a/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java b/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java index 049b566617b5..e8ee4f2c9461 100644 --- a/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java @@ -27,6 +27,8 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; @@ -75,6 +77,7 @@ public class UploadFilesActivity extends FileActivity implements private ArrayAdapter mDirectories; private File mCurrentDir = null; private boolean mSelectAll = false; + private boolean mLocalFolderPickerMode = false; private LocalFileListFragment mFileListFragment; private Button mCancelBtn; protected Button mUploadBtn; @@ -88,6 +91,10 @@ public class UploadFilesActivity extends FileActivity implements public static final String EXTRA_CHOSEN_FILES = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES"; + public static final String EXTRA_ACTION = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_ACTION"; + public final static String KEY_LOCAL_FOLDER_PICKER_MODE = UploadFilesActivity.class.getCanonicalName() + + ".LOCAL_FOLDER_PICKER_MODE"; + public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER; public static final int RESULT_OK_AND_DO_NOTHING = 2; public static final int RESULT_OK_AND_DELETE = 3; @@ -106,6 +113,11 @@ public void onCreate(Bundle savedInstanceState) { Log_OC.d(TAG, "onCreate() start"); super.onCreate(savedInstanceState); + Bundle extras = getIntent().getExtras(); + if (extras != null) { + mLocalFolderPickerMode = extras.getBoolean(KEY_LOCAL_FOLDER_PICKER_MODE, false); + } + if(savedInstanceState != null) { mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.KEY_DIRECTORY_PATH, Environment .getExternalStorageDirectory().getAbsolutePath())); @@ -130,9 +142,14 @@ public void onCreate(Bundle savedInstanceState) { // Inflate and set the layout view setContentView(R.layout.upload_files_layout); + if (mLocalFolderPickerMode) { + findViewById(R.id.upload_options).setVisibility(View.GONE); + ((AppCompatButton) findViewById(R.id.upload_files_btn_upload)) + .setText(R.string.uploader_btn_alternative_text); + } + mFileListFragment = (LocalFileListFragment) getSupportFragmentManager().findFragmentById(R.id.local_files_list); - // Set input controllers mCancelBtn = (Button) findViewById(R.id.upload_files_btn_cancel); mCancelBtn.setOnClickListener(this); @@ -190,10 +207,15 @@ public static void startUploadActivityForResult(Activity activity, Account accou public boolean onCreateOptionsMenu(Menu menu) { mOptionsMenu = menu; getMenuInflater().inflate(R.menu.upload_files_picker, menu); - MenuItem selectAll = menu.findItem(R.id.action_select_all); - setSelectAllMenuItem(selectAll, mSelectAll); + + if(!mLocalFolderPickerMode) { + MenuItem selectAll = menu.findItem(R.id.action_select_all); + setSelectAllMenuItem(selectAll, mSelectAll); + } + MenuItem switchView = menu.findItem(R.id.action_switch_view); switchView.setTitle(isGridView() ? R.string.action_switch_list_view : R.string.action_switch_grid_view); + return super.onCreateOptionsMenu(menu); } @@ -215,9 +237,6 @@ public boolean onOptionsItemSelected(MenuItem item) { break; } case R.id.action_sort: { - // Read sorting order, default to sort by name ascending - Integer sortOrder = PreferenceManager.getSortOrder(this); - FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.addToBackStack(null); @@ -290,7 +309,6 @@ public boolean onNavigationItemSelected(int itemPosition, long itemId) { } return true; } - @Override public void onBackPressed() { @@ -308,9 +326,10 @@ public void onBackPressed() { } // invalidate checked state when navigating directories - setSelectAllMenuItem(mOptionsMenu.findItem(R.id.action_select_all), false); + if(!mLocalFolderPickerMode) { + setSelectAllMenuItem(mOptionsMenu.findItem(R.id.action_select_all), false); + } } - @Override protected void onSaveInstanceState(Bundle outState) { @@ -361,15 +380,16 @@ private void setSelectAllMenuItem(MenuItem selectAll, boolean checked) { } } - - // Custom array adapter to override text colors + /** + * Custom array adapter to override text colors + */ private class CustomArrayAdapter extends ArrayAdapter { public CustomArrayAdapter(UploadFilesActivity ctx, int view) { super(ctx, view); } - - public View getView(int position, View convertView, ViewGroup parent) { + + public @NonNull View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { View v = super.getView(position, convertView, parent); ((TextView) v).setTextColor(getResources().getColorStateList( @@ -377,8 +397,7 @@ public View getView(int position, View convertView, ViewGroup parent) { return v; } - public View getDropDownView(int position, View convertView, - ViewGroup parent) { + public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) { View v = super.getDropDownView(position, convertView, parent); ((TextView) v).setTextColor(getResources().getColorStateList( @@ -386,7 +405,6 @@ public View getDropDownView(int position, View convertView, return v; } - } /** @@ -394,9 +412,11 @@ public View getDropDownView(int position, View convertView, */ @Override public void onDirectoryClick(File directory) { - // invalidate checked state when navigating directories - MenuItem selectAll = mOptionsMenu.findItem(R.id.action_select_all); - setSelectAllMenuItem(selectAll, false); + if(!mLocalFolderPickerMode) { + // invalidate checked state when navigating directories + MenuItem selectAll = mOptionsMenu.findItem(R.id.action_select_all); + setSelectAllMenuItem(selectAll, false); + } pushDirname(directory); ActionBar actionBar = getSupportActionBar(); @@ -419,6 +439,14 @@ public File getInitialDirectory() { return mCurrentDir; } + /** + * {@inheritDoc} + */ + @Override + public boolean isFolderPickerMode() { + return mLocalFolderPickerMode; + } + /** * Performs corresponding action when user presses 'Cancel' or 'Upload' button * @@ -432,7 +460,17 @@ public void onClick(View v) { finish(); } else if (v.getId() == R.id.upload_files_btn_upload) { - new CheckAvailableSpaceTask().execute(mBehaviourSpinner.getSelectedItemPosition()==0); + if(mLocalFolderPickerMode) { + Intent data = new Intent(); + if(mCurrentDir != null) { + data.putExtra(EXTRA_CHOSEN_FILES, mCurrentDir.getAbsolutePath()); + } + setResult(RESULT_OK, data); + + finish(); + } else { + new CheckAvailableSpaceTask().execute(mBehaviourSpinner.getSelectedItemPosition() == 0); + } } } @@ -445,7 +483,7 @@ public void onClick(View v) { private class CheckAvailableSpaceTask extends AsyncTask { /** - * Updates the UI before trying the movement + * Updates the UI before trying the movement. */ @Override protected void onPreExecute () { diff --git a/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java index 16c7949447b3..320a04fe810d 100755 --- a/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java @@ -22,7 +22,6 @@ package com.owncloud.android.ui.activity; import android.accounts.Account; -import android.accounts.AccountManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -30,9 +29,11 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.preference.PreferenceManager; import android.support.design.widget.BottomNavigationView; import android.support.v4.app.FragmentTransaction; import android.view.Menu; @@ -41,21 +42,22 @@ import android.view.View; import android.widget.Toast; +import com.evernote.android.job.JobRequest; import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.UploadsStorageManager; import com.owncloud.android.db.OCUpload; -import com.owncloud.android.db.UploadResult; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.jobs.FilesSyncJob; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.operations.CheckCurrentCredentialsOperation; import com.owncloud.android.ui.fragment.UploadListFragment; -import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.AnalyticsUtils; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.FilesSyncHelper; import com.owncloud.android.utils.MimeTypeUtil; import java.io.File; @@ -74,8 +76,12 @@ public class UploadListActivity extends FileActivity implements UploadListFragme private static final String SCREEN_NAME = "Uploads"; + private static final String EXPERT_MODE = "expert_mode"; + private UploadMessagesReceiver mUploadMessagesReceiver; + private Menu mMenu; + @Override public void showFiles(boolean onDeviceOnly) { super.showFiles(onDeviceOnly); @@ -211,9 +217,14 @@ public boolean onOptionsItemSelected(MenuItem item) { } else { openDrawer(); } + break; + case R.id.action_retry_uploads: FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); requester.retryFailedUploads(this, null, null); + if (mMenu != null) { + mMenu.removeItem(R.id.action_retry_uploads); + } break; case R.id.action_clear_failed_uploads: @@ -234,6 +245,19 @@ public boolean onOptionsItemSelected(MenuItem item) { uploadListFragment.updateUploads(); break; + case R.id.action_force_rescan: + new JobRequest.Builder(FilesSyncJob.TAG) + .setExact(1_000L) + .setUpdateCurrent(false) + .build() + .schedule(); + + if (mMenu != null) { + mMenu.removeItem(R.id.action_force_rescan); + } + + break; + default: retval = super.onOptionsItemSelected(item); } @@ -243,8 +267,14 @@ public boolean onOptionsItemSelected(MenuItem item) { @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.upload_list_menu, menu); + SharedPreferences appPrefs = + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + if (appPrefs.getBoolean(EXPERT_MODE, false)) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.upload_list_menu, menu); + mMenu = menu; + } + return true; } @@ -252,17 +282,7 @@ public boolean onCreateOptionsMenu(Menu menu) { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == FileActivity.REQUEST_CODE__UPDATE_CREDENTIALS && resultCode == RESULT_OK) { - // Retry uploads of the updated account - Account account = AccountUtils.getOwnCloudAccountByName( - this, - data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME) - ); - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.retryFailedUploads( - this, - account, - UploadResult.CREDENTIAL_ERROR - ); + FilesSyncHelper.restartJobsIfNeeded(); } } @@ -283,8 +303,7 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe } else { // already updated -> just retry! - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.retryFailedUploads(this, account, UploadResult.CREDENTIAL_ERROR); + FilesSyncHelper.restartJobsIfNeeded(); } } else { diff --git a/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java b/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java index 3f18ad26a037..8e43c32e5f5f 100644 --- a/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java @@ -63,7 +63,6 @@ import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.PushConfigurationState; import com.owncloud.android.datamodel.SyncedFolderProvider; -import com.owncloud.android.datamodel.UploadsStorageManager; import com.owncloud.android.lib.common.UserInfo; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -410,16 +409,11 @@ public void onClick(DialogInterface dialogInterface, int i) { contentResolver); syncedFolderProvider.deleteSyncFoldersForAccount(account); - UploadsStorageManager uploadsStorageManager = new UploadsStorageManager( - contentResolver, getActivity()); - uploadsStorageManager.cancelPendingAutoUploadJobsForAccount(account); - uploadsStorageManager.removeAccountUploads(account); - // disable daily backup ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider( contentResolver); - arbitraryDataProvider.storeOrUpdateKeyValue(account, + arbitraryDataProvider.storeOrUpdateKeyValue(account.name, ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP, "false"); @@ -433,7 +427,7 @@ public void onClick(DialogInterface dialogInterface, int i) { PushConfigurationState pushArbitraryData = gson.fromJson(arbitraryDataPushString, PushConfigurationState.class); pushArbitraryData.setShouldBeDeleted(true); - arbitraryDataProvider.storeOrUpdateKeyValue(account, PushUtils.KEY_PUSH, + arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PushUtils.KEY_PUSH, gson.toJson(pushArbitraryData)); EventBus.getDefault().post(new TokenPushEvent()); } diff --git a/src/main/java/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index 0d9d61610bb8..ce42214e3509 100755 --- a/src/main/java/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -1,21 +1,21 @@ /** - * ownCloud Android client application + * ownCloud Android client application * - * @author LukeOwncloud - * @author masensio - * Copyright (C) 2016 ownCloud Inc. + * @author LukeOwncloud + * @author masensio + * Copyright (C) 2016 ownCloud Inc. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package com.owncloud.android.ui.adapter; @@ -26,7 +26,6 @@ import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; @@ -34,7 +33,6 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import android.widget.Toast; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; @@ -43,7 +41,6 @@ import com.owncloud.android.datamodel.UploadsStorageManager; import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus; import com.owncloud.android.db.OCUpload; -import com.owncloud.android.db.UploadResult; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.utils.Log_OC; @@ -102,10 +99,10 @@ public int getGroupItemCount() { @Override public int compare(OCUpload upload1, OCUpload upload2) { - if (upload1 == null){ + if (upload1 == null) { return -1; } - if (upload2 == null){ + if (upload2 == null) { return 1; } if (UploadStatus.UPLOAD_IN_PROGRESS.equals(upload1.getUploadStatus())) { @@ -153,7 +150,7 @@ public ExpandableUploadListAdapter(FileActivity parentActivity) { mUploadGroups[0] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_current_uploads)) { @Override public void refresh() { - items = mUploadsStorageManager.getCurrentAndPendingUploads(); + items = mUploadsStorageManager.getCurrentAndPendingUploadsForCurrentAccount(); Arrays.sort(items, comparator); } @@ -165,7 +162,7 @@ public int getGroupIcon() { mUploadGroups[1] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_failed_uploads)) { @Override public void refresh() { - items = mUploadsStorageManager.getFailedButNotDelayedUploads(); + items = mUploadsStorageManager.getFailedButNotDelayedUploadsForCurrentAccount(); Arrays.sort(items, comparator); } @@ -178,7 +175,7 @@ public int getGroupIcon() { mUploadGroups[2] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_finished_uploads)) { @Override public void refresh() { - items = mUploadsStorageManager.getFinishedUploads(); + items = mUploadsStorageManager.getFinishedUploadsForCurrentAccount(); Arrays.sort(items, comparator); } @@ -292,33 +289,33 @@ private View getView(OCUpload[] uploadsItems, int position, View convertView, Vi /// ... unbind the old progress bar, if any; ... if (mProgressListener != null) { binder.removeDatatransferProgressListener( - mProgressListener, - mProgressListener.getUpload() // the one that was added + mProgressListener, + mProgressListener.getUpload() // the one that was added ); } /// ... then, bind the current progress bar to listen for updates mProgressListener = new ProgressListener(upload, progressBar); binder.addDatatransferProgressListener( - mProgressListener, - upload + mProgressListener, + upload ); } else { /// not really uploading; stop listening progress if view is reused! if (convertView != null && mProgressListener != null && - mProgressListener.isWrapping(progressBar)) { + mProgressListener.isWrapping(progressBar)) { binder.removeDatatransferProgressListener( - mProgressListener, - mProgressListener.getUpload() // the one that was added + mProgressListener, + mProgressListener.getUpload() // the one that was added ); mProgressListener = null; } } } else { Log_OC.w( - TAG, - "FileUploaderBinder not ready yet for upload " + upload.getRemotePath() + TAG, + "FileUploaderBinder not ready yet for upload " + upload.getRemotePath() ); } uploadDateTextView.setVisibility(View.GONE); @@ -336,13 +333,14 @@ private View getView(OCUpload[] uploadsItems, int position, View convertView, Vi } statusTextView.setText(status); + /// bind listeners to perform actions /// bind listeners to perform actions ImageButton rightButton = (ImageButton) view.findViewById(R.id.upload_right_button); if (upload.getUploadStatus() == UploadStatus.UPLOAD_IN_PROGRESS) { //Cancel rightButton.setImageResource(R.drawable.ic_action_cancel_grey); rightButton.setVisibility(View.VISIBLE); - rightButton.setOnClickListener(new OnClickListener() { + rightButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FileUploader.FileUploaderBinder uploaderBinder = mParentActivity.getFileUploaderBinder(); @@ -357,7 +355,7 @@ public void onClick(View v) { //Delete rightButton.setImageResource(R.drawable.ic_action_delete_grey); rightButton.setVisibility(View.VISIBLE); - rightButton.setOnClickListener(new OnClickListener() { + rightButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mUploadsStorageManager.removeUpload(upload); @@ -368,41 +366,8 @@ public void onClick(View v) { } else { // UploadStatus.UPLOAD_SUCCESS rightButton.setVisibility(View.INVISIBLE); } - - // retry - if (upload.getUploadStatus() == UploadStatus.UPLOAD_FAILED) { - if (UploadResult.CREDENTIAL_ERROR.equals(upload.getLastResult())) { - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mParentActivity.getFileOperationsHelper().checkCurrentCredentials( - upload.getAccount(mParentActivity) - ); - } - }); - - } else { - // not a credentials error - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - File file = new File(upload.getLocalPath()); - if (file.exists()) { - FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); - requester.retry(mParentActivity, upload); - refreshView(); - } else { - final String message = String.format( - mParentActivity.getString(R.string.local_file_not_found_toast) - ); - Toast.makeText(mParentActivity, message, Toast.LENGTH_SHORT).show(); - } - } - }); - } - } else { - view.setOnClickListener(null); - } + + view.setOnClickListener(null); /// Set icon or thumbnail ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail); @@ -509,7 +474,7 @@ public void onClick(View v) { * the given upload. * * @param upload Upload to describe. - * @return Text describing the status of the given upload. + * @return Text describing the status of the given upload. */ private String getStatusText(OCUpload upload) { @@ -533,37 +498,37 @@ private String getStatusText(OCUpload upload) { switch (upload.getLastResult()) { case CREDENTIAL_ERROR: status = mParentActivity.getString( - R.string.uploads_view_upload_status_failed_credentials_error + R.string.uploads_view_upload_status_failed_credentials_error ); break; case FOLDER_ERROR: status = mParentActivity.getString( - R.string.uploads_view_upload_status_failed_folder_error + R.string.uploads_view_upload_status_failed_folder_error ); break; case FILE_NOT_FOUND: status = mParentActivity.getString( - R.string.uploads_view_upload_status_failed_localfile_error + R.string.uploads_view_upload_status_failed_localfile_error ); break; case FILE_ERROR: status = mParentActivity.getString( - R.string.uploads_view_upload_status_failed_file_error + R.string.uploads_view_upload_status_failed_file_error ); break; case PRIVILEDGES_ERROR: status = mParentActivity.getString( - R.string.uploads_view_upload_status_failed_permission_error + R.string.uploads_view_upload_status_failed_permission_error ); break; case NETWORK_CONNECTION: status = mParentActivity.getString( - R.string.uploads_view_upload_status_failed_connection_error + R.string.uploads_view_upload_status_failed_connection_error ); break; case DELAYED_FOR_WIFI: status = mParentActivity.getString( - R.string.uploads_view_upload_status_waiting_for_wifi + R.string.uploads_view_upload_status_waiting_for_wifi ); break; case DELAYED_FOR_CHARGING: @@ -572,32 +537,35 @@ private String getStatusText(OCUpload upload) { break; case CONFLICT_ERROR: status = mParentActivity.getString( - R.string.uploads_view_upload_status_conflict + R.string.uploads_view_upload_status_conflict ); break; case SERVICE_INTERRUPTED: - status = mParentActivity.getString( - R.string.uploads_view_upload_status_service_interrupted + status = mParentActivity.getString( + R.string.uploads_view_upload_status_service_interrupted ); break; case UNKNOWN: status = mParentActivity.getString( - R.string.uploads_view_upload_status_unknown_fail + R.string.uploads_view_upload_status_unknown_fail ); break; case CANCELLED: // should not get here ; cancelled uploads should be wiped out status = mParentActivity.getString( - R.string.uploads_view_upload_status_cancelled + R.string.uploads_view_upload_status_cancelled ); break; case UPLOADED: // should not get here ; status should be UPLOAD_SUCCESS - status = mParentActivity.getString(R.string.uploads_view_upload_status_succeeded); + status = mParentActivity.getString(R.string.uploads_view_upload_status_succeeded); break; case MAINTENANCE_MODE: status = mParentActivity.getString(R.string.maintenance_mode); break; + case LOCK_FAILED: + status = mParentActivity.getString(R.string.lock_failed); + break; default: status = "Naughty devs added a new fail result but no description for the user"; break; @@ -747,8 +715,8 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, lo public boolean isWrapping(ProgressBar progressBar) { ProgressBar wrappedProgressBar = mProgressBar.get(); return ( - wrappedProgressBar != null && - wrappedProgressBar == progressBar // on purpose; don't replace with equals + wrappedProgressBar != null && + wrappedProgressBar == progressBar // on purpose; don't replace with equals ); } diff --git a/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java index 69f036585bea..01a683642318 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java @@ -41,6 +41,7 @@ import com.owncloud.android.utils.MimeTypeUtil; import java.io.File; +import java.io.FileFilter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -58,10 +59,12 @@ public class LocalFileListAdapter extends BaseAdapter implements FilterableListA private Context mContext; private File[] mFiles = null; - private Vector mFilesAll = new Vector(); + private Vector mFilesAll = new Vector<>(); + private boolean mLocalFolderPicker; - public LocalFileListAdapter(File directory, Context context) { + public LocalFileListAdapter(boolean localFolderPickerMode, File directory, Context context) { mContext = context; + mLocalFolderPicker = localFolderPickerMode; // Read sorting order, default to sort by name ascending FileStorageUtils.mSortOrder = PreferenceManager.getSortOrder(context); @@ -272,7 +275,11 @@ public boolean isEmpty() { * @param directory New file to adapt. Can be NULL, meaning "no content to adapt". */ public void swapDirectory(final File directory) { - mFiles = (directory != null ? directory.listFiles() : null); + if(mLocalFolderPicker) { + mFiles = (directory != null ? getFolders(directory) : null); + } else { + mFiles = (directory != null ? directory.listFiles() : null); + } if (mFiles != null) { Arrays.sort(mFiles, new Comparator() { @Override @@ -288,7 +295,6 @@ public int compare(File lhs, File rhs) { private int compareNames(File lhs, File rhs) { return lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase()); } - }); mFiles = FileStorageUtils.sortLocalFolder(mFiles); @@ -317,6 +323,15 @@ public void setSortOrder(Integer order, boolean ascending) { notifyDataSetChanged(); } + private File[] getFolders(final File directory) { + return directory.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return file.isDirectory(); + } + }); + } + public void filter(String text){ if(text.isEmpty()){ mFiles = mFilesAll.toArray(new File[1]); diff --git a/src/main/java/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java similarity index 64% rename from src/main/java/com/owncloud/android/ui/adapter/FolderSyncAdapter.java rename to src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java index 92ca34aefd0d..d564e596217a 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java @@ -1,22 +1,22 @@ /** * Nextcloud Android client application * - * @author Andy Scherzinger - * Copyright (C) 2016 Andy Scherzinger - * Copyright (C) 2016 Nextcloud + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. * - * You should have received a copy of the GNU Affero General Public - * License along with this program. If not, see . + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . */ package com.owncloud.android.ui.adapter; @@ -29,10 +29,12 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; +import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.utils.ThemeUtils; @@ -44,7 +46,7 @@ /** * Adapter to display all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncAdapter extends SectionedRecyclerViewAdapter { +public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter { private final Context mContext; private final int mGridWidth; @@ -53,19 +55,20 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter mSyncFolderItems; private final boolean mLight; - public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, boolean light) { + public SyncedFolderAdapter(Context context, int gridWidth, ClickListener listener, boolean light) { mContext = context; mGridWidth = gridWidth; mGridTotal = gridWidth * 2; mListener = listener; mSyncFolderItems = new ArrayList<>(); mLight = light; + + shouldShowHeadersForEmptySections(true); } public void setSyncFolderItems(List syncFolderItems) { mSyncFolderItems.clear(); mSyncFolderItems.addAll(syncFolderItems); - notifyDataSetChanged(); } public void setSyncFolderItem(int location, SyncedFolderDisplayItem syncFolderItem) { @@ -73,6 +76,16 @@ public void setSyncFolderItem(int location, SyncedFolderDisplayItem syncFolderIt notifyDataSetChanged(); } + public void addSyncFolderItem(SyncedFolderDisplayItem syncFolderItem) { + mSyncFolderItems.add(syncFolderItem); + notifyDataSetChanged(); + } + + public void removeItem(int section) { + mSyncFolderItems.remove(section); + notifyDataSetChanged(); + } + @Override public int getSectionCount() { return mSyncFolderItems.size(); @@ -87,18 +100,39 @@ public int getItemCount(int section) { } } + public SyncedFolderDisplayItem get(int section) { + return mSyncFolderItems.get(section); + } + @Override public void onBindHeaderViewHolder(final MainViewHolder holder, final int section) { + holder.mainHeaderContainer.setVisibility(View.VISIBLE); + holder.title.setText(mSyncFolderItems.get(section).getFolderName()); + + if (MediaFolderType.VIDEO == mSyncFolderItems.get(section).getType()) { + holder.type.setImageResource(R.drawable.ic_video_18dp); + } else if (MediaFolderType.IMAGE == mSyncFolderItems.get(section).getType()) { + holder.type.setImageResource(R.drawable.ic_image_18dp); + } else { + holder.type.setImageResource(R.drawable.ic_folder_star_18dp); + } + holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); - holder.syncStatusButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); - setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); - mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); - } + holder.syncStatusButton.setOnClickListener(v -> { + mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); + setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); + mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); + }); + setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); + + holder.syncStatusButton.setVisibility(View.VISIBLE); + holder.syncStatusButton.setTag(section); + holder.syncStatusButton.setOnClickListener(v -> { + mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); + setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); + mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); }); setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); @@ -107,18 +141,14 @@ public void onClick(View v) { } else { holder.menuButton.setVisibility(View.VISIBLE); holder.menuButton.setTag(section); - holder.menuButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mListener.onSyncFolderSettingsClick(section, mSyncFolderItems.get(section)); - } - }); + holder.menuButton.setOnClickListener(v -> mListener.onSyncFolderSettingsClick(section, + mSyncFolderItems.get(section))); } } + @Override public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { - if (mSyncFolderItems.get(section).getFilePaths() != null) { File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition)); @@ -148,14 +178,6 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos holder.counterBar.setVisibility(View.GONE); holder.thumbnailDarkener.setVisibility(View.GONE); } - - //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos)); - //holder.itemView.setOnClickListener(this); - } else { - holder.itemView.setTag(relativePosition % mGridWidth); - holder.counterValue.setText(Long.toString(0)); - holder.counterBar.setVisibility(View.VISIBLE); - holder.thumbnailDarkener.setVisibility(View.VISIBLE); } } @@ -163,7 +185,7 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate( viewType == VIEW_TYPE_HEADER ? - R.layout.folder_sync_item_header : R.layout.grid_sync_item, parent, false); + R.layout.synced_folders_item_header : R.layout.grid_sync_item, parent, false); return new MainViewHolder(v); } @@ -175,16 +197,21 @@ public interface ClickListener { static class MainViewHolder extends RecyclerView.ViewHolder { private final ImageView image; private final TextView title; + private final ImageView type; private final ImageButton menuButton; private final ImageButton syncStatusButton; private final LinearLayout counterBar; private final TextView counterValue; private final ImageView thumbnailDarkener; + private final RelativeLayout mainHeaderContainer; + private MainViewHolder(View itemView) { super(itemView); + mainHeaderContainer = (RelativeLayout) itemView.findViewById(R.id.header_container); image = (ImageView) itemView.findViewById(R.id.thumbnail); title = (TextView) itemView.findViewById(R.id.title); + type = (ImageView) itemView.findViewById(R.id.type); menuButton = (ImageButton) itemView.findViewById(R.id.settingsButton); syncStatusButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); counterBar = (LinearLayout) itemView.findViewById(R.id.counterLayout); @@ -194,7 +221,7 @@ private MainViewHolder(View itemView) { } private void setSyncButtonActiveIcon(ImageButton syncStatusButton, boolean enabled) { - if(enabled) { + if (enabled) { syncStatusButton.setImageDrawable(ThemeUtils.tintDrawable(R.drawable.ic_cloud_sync_on, ThemeUtils.primaryColor())); } else { diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java index 1ed1b02a34f5..f60552ebfbbe 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java @@ -228,7 +228,9 @@ private void requestUpload(Account account, String localPath, String remotePath, behaviour, mimeType, false, // do not create parent folder if not existent - UploadFileOperation.CREATED_BY_USER + UploadFileOperation.CREATED_BY_USER, + false, + false ); } diff --git a/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.java b/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.java index aec8ce5dc4f5..d48937979d8e 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.java +++ b/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.java @@ -47,8 +47,10 @@ public void onCreate(Bundle savedInstanceState) { setCancelable(false); } - public LoadingDialog(String message) { - this.mMessage = message; + public static LoadingDialog newInstance(String message) { + LoadingDialog loadingDialog = new LoadingDialog(); + loadingDialog.mMessage = message; + return loadingDialog; } @Override diff --git a/src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.java b/src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.java index 7f6ef0382df2..8f01e4fa16dc 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.java +++ b/src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.java @@ -105,7 +105,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mView = inflater.inflate(R.layout.sorting_order_fragment, container, false); setupDialogElements(mView); - setupListeners(mView); + setupListeners(); return mView; } @@ -208,10 +208,8 @@ private void colorActiveSortingIconAndText(ImageButton imageButton, TextView tex /** * setup all listeners. - * - * @param view the parent view */ - private void setupListeners(View view) { + private void setupListeners() { mCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { diff --git a/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java index 772746f86ac1..672a08d1841c 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java +++ b/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -25,6 +25,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Typeface; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -41,13 +42,19 @@ import android.widget.TextView; import com.owncloud.android.R; +import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.FolderPickerActivity; +import com.owncloud.android.ui.activity.UploadFilesActivity; import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.ThemeUtils; +import java.io.File; + +import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID; + /** * Dialog to show the preferences/configuration of a synced folder allowing the user to change the different parameters. */ @@ -57,6 +64,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment { public static final String SYNCED_FOLDER_PARCELABLE = "SyncedFolderParcelable"; private static final String BEHAVIOUR_DIALOG_STATE = "BEHAVIOUR_DIALOG_STATE"; public static final int REQUEST_CODE__SELECT_REMOTE_FOLDER = 0; + public static final int REQUEST_CODE__SELECT_LOCAL_FOLDER = 1; private CharSequence[] mUploadBehaviorItemStrings; @@ -67,6 +75,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment { private AppCompatCheckBox mUploadUseSubfoldersCheckbox; private TextView mUploadBehaviorSummary; private TextView mLocalFolderPath; + private TextView mLocalFolderSummary; private TextView mRemoteFolderSummary; private SyncedFolderParcelable mSyncedFolder; @@ -85,7 +94,7 @@ public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderDisp Bundle args = new Bundle(); args.putParcelable(SYNCED_FOLDER_PARCELABLE, new SyncedFolderParcelable(syncedFolder, section)); dialogFragment.setArguments(args); - dialogFragment.setStyle(STYLE_NORMAL,R.style.Theme_ownCloud_Dialog); + dialogFragment.setStyle(STYLE_NORMAL, R.style.Theme_ownCloud_Dialog); return dialogFragment; } @@ -116,7 +125,7 @@ public void onCreate(Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log_OC.d(TAG, "onCreateView, savedInstanceState is " + savedInstanceState); - mView = inflater.inflate(R.layout.folder_sync_settings_layout, container, false); + mView = inflater.inflate(R.layout.synced_folders_settings_layout, container, false); setupDialogElements(mView); setupListeners(mView); @@ -132,20 +141,49 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa private void setupDialogElements(View view) { int accentColor = ThemeUtils.primaryAccentColor(); + if (mSyncedFolder.getType().getId() > MediaFolderType.CUSTOM.getId()) { + // hide local folder chooser and delete for non-custom folders + view.findViewById(R.id.local_folder_container).setVisibility(View.GONE); + view.findViewById(R.id.delete).setVisibility(View.GONE); + } else if (mSyncedFolder.getId() <= UNPERSISTED_ID) { + // Hide delete/enabled for unpersisted custom folders + view.findViewById(R.id.delete).setVisibility(View.GONE); + view.findViewById(R.id.sync_enabled).setVisibility(View.GONE); + + // auto set custom folder to enabled + mSyncedFolder.setEnabled(true); + + // switch text to create headline + ((TextView) view.findViewById(R.id.synced_folders_settings_title)) + .setText(R.string.autoupload_create_new_custom_folder); + + // disable save button + view.findViewById(R.id.save).setEnabled(false); + } else { + view.findViewById(R.id.local_folder_container).setVisibility(View.GONE); + } + // find/saves UI elements mEnabledSwitch = (SwitchCompat) view.findViewById(R.id.sync_enabled); ThemeUtils.tintSwitch(mEnabledSwitch, accentColor); - mLocalFolderPath = (TextView) view.findViewById(R.id.folder_sync_settings_local_folder_path); + mLocalFolderPath = (TextView) view.findViewById(R.id.synced_folders_settings_local_folder_path); + mLocalFolderSummary = (TextView) view.findViewById(R.id.local_folder_summary); mRemoteFolderSummary = (TextView) view.findViewById(R.id.remote_folder_summary); mUploadOnWifiCheckbox = (AppCompatCheckBox) view.findViewById(R.id.setting_instant_upload_on_wifi_checkbox); ThemeUtils.tintCheckbox(mUploadOnWifiCheckbox, accentColor); - mUploadOnChargingCheckbox = (AppCompatCheckBox) view.findViewById( - R.id.setting_instant_upload_on_charging_checkbox); - ThemeUtils.tintCheckbox(mUploadOnChargingCheckbox, accentColor); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + view.findViewById(R.id.setting_instant_upload_on_charging_container).setVisibility(View.GONE); + } else { + view.findViewById(R.id.setting_instant_upload_on_charging_container).setVisibility(View.VISIBLE); + + mUploadOnChargingCheckbox = (AppCompatCheckBox) view.findViewById( + R.id.setting_instant_upload_on_charging_checkbox); + ThemeUtils.tintCheckbox(mUploadOnChargingCheckbox, accentColor); + } mUploadUseSubfoldersCheckbox = (AppCompatCheckBox) view.findViewById( R.id.setting_instant_upload_path_use_subfolders_checkbox); @@ -161,18 +199,30 @@ private void setupDialogElements(View view) { // Set values setEnabled(mSyncedFolder.getEnabled()); - mLocalFolderPath.setText( - DisplayUtils.createTextWithSpan( - String.format( - getString(R.string.folder_sync_preferences_folder_path), - mSyncedFolder.getLocalPath()), - mSyncedFolder.getFolderName(), - new StyleSpan(Typeface.BOLD))); - mRemoteFolderSummary.setText(mSyncedFolder.getRemotePath()); + if (mSyncedFolder.getLocalPath() != null && mSyncedFolder.getLocalPath().length() > 0) { + mLocalFolderPath.setText( + DisplayUtils.createTextWithSpan( + String.format( + getString(R.string.synced_folders_preferences_folder_path), + mSyncedFolder.getLocalPath()), + mSyncedFolder.getFolderName(), + new StyleSpan(Typeface.BOLD))); + mLocalFolderSummary.setText(mSyncedFolder.getLocalPath()); + } else { + mLocalFolderSummary.setText(R.string.choose_local_folder); + } + + if (mSyncedFolder.getLocalPath() != null && mSyncedFolder.getLocalPath().length() > 0) { + mRemoteFolderSummary.setText(mSyncedFolder.getRemotePath()); + } else { + mRemoteFolderSummary.setText(R.string.choose_remote_folder); + } mUploadOnWifiCheckbox.setChecked(mSyncedFolder.getWifiOnly()); - mUploadOnChargingCheckbox.setChecked(mSyncedFolder.getChargingOnly()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + mUploadOnChargingCheckbox.setChecked(mSyncedFolder.getChargingOnly()); + } mUploadUseSubfoldersCheckbox.setChecked(mSyncedFolder.getSubfolderByDate()); mUploadBehaviorSummary.setText(mUploadBehaviorItemStrings[mSyncedFolder.getUploadActionInteger()]); @@ -198,6 +248,35 @@ private void setEnabled(boolean enabled) { public void setRemoteFolderSummary(String path) { mSyncedFolder.setRemotePath(path); mRemoteFolderSummary.setText(path); + checkAndUpdateSaveButtonState(); + } + + /** + * set (new) local path on activity result of the folder picker activity. The result gets originally propagated + * to the underlying activity since the picker is an activity and the result can't get passed to the dialog + * fragment directly. + * + * @param path the remote path to be set + */ + public void setLocalFolderSummary(String path) { + mSyncedFolder.setLocalPath(path); + mLocalFolderSummary.setText(path); + mLocalFolderPath.setText( + DisplayUtils.createTextWithSpan( + String.format( + getString(R.string.synced_folders_preferences_folder_path), + mSyncedFolder.getLocalPath()), + new File(mSyncedFolder.getLocalPath()).getName(), + new StyleSpan(Typeface.BOLD))); + checkAndUpdateSaveButtonState(); + } + + private void checkAndUpdateSaveButtonState() { + if (mSyncedFolder.getLocalPath() != null && mSyncedFolder.getRemotePath() != null) { + mView.findViewById(R.id.save).setEnabled(true); + } else { + mView.findViewById(R.id.save).setEnabled(false); + } } /** @@ -208,6 +287,7 @@ public void setRemoteFolderSummary(String path) { private void setupListeners(View view) { mSave.setOnClickListener(new OnSyncedFolderSaveClickListener()); mCancel.setOnClickListener(new OnSyncedFolderCancelClickListener()); + view.findViewById(R.id.delete).setOnClickListener(new OnSyncedFolderDeleteClickListener()); view.findViewById(R.id.setting_instant_upload_on_wifi_container).setOnClickListener( new OnClickListener() { @@ -218,14 +298,17 @@ public void onClick(View v) { } }); - view.findViewById(R.id.setting_instant_upload_on_charging_container).setOnClickListener( - new OnClickListener() { - @Override - public void onClick(View v) { - mSyncedFolder.setChargingOnly(!mSyncedFolder.getChargingOnly()); - mUploadOnChargingCheckbox.toggle(); - } - }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + + view.findViewById(R.id.setting_instant_upload_on_charging_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + mSyncedFolder.setChargingOnly(!mSyncedFolder.getChargingOnly()); + mUploadOnChargingCheckbox.toggle(); + } + }); + } view.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setOnClickListener( new OnClickListener() { @@ -240,12 +323,19 @@ public void onClick(View v) { @Override public void onClick(View v) { Intent action = new Intent(getActivity(), FolderPickerActivity.class); - action.putExtra( - FolderPickerActivity.EXTRA_ACTION, getResources().getText(R.string.choose_remote_folder)); getActivity().startActivityForResult(action, REQUEST_CODE__SELECT_REMOTE_FOLDER); } }); + view.findViewById(R.id.local_folder_container).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent action = new Intent(getActivity(), UploadFilesActivity.class); + action.putExtra(UploadFilesActivity.KEY_LOCAL_FOLDER_PICKER_MODE, true); + getActivity().startActivityForResult(action, REQUEST_CODE__SELECT_LOCAL_FOLDER); + } + }); + view.findViewById(R.id.sync_enabled).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -330,10 +420,20 @@ public void onClick(View v) { } } + private class OnSyncedFolderDeleteClickListener implements OnClickListener { + @Override + public void onClick(View v) { + dismiss(); + ((OnSyncedFolderPreferenceListener) getActivity()).onDeleteSyncedFolderPreference(mSyncedFolder); + } + } + public interface OnSyncedFolderPreferenceListener { void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder); void onCancelSyncedFolderPreference(); + + void onDeleteSyncedFolderPreference(SyncedFolderParcelable syncedFolder); } @Override diff --git a/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java index f5d9f499ba57..d03922b76921 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java +++ b/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.os.Parcelable; +import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.files.services.FileUploader; @@ -38,6 +39,7 @@ public class SyncedFolderParcelable implements Parcelable { private Boolean mEnabled = false; private Boolean mSubfolderByDate = false; private Integer mUploadAction; + private MediaFolderType mType; private long mId; private String mAccount; private int mSection; @@ -54,6 +56,7 @@ public SyncedFolderParcelable(SyncedFolderDisplayItem syncedFolderDisplayItem, i mChargingOnly = syncedFolderDisplayItem.getChargingOnly(); mEnabled = syncedFolderDisplayItem.isEnabled(); mSubfolderByDate = syncedFolderDisplayItem.getSubfolderByDate(); + mType = syncedFolderDisplayItem.getType(); mAccount = syncedFolderDisplayItem.getAccount(); mUploadAction = syncedFolderDisplayItem.getUploadAction(); mSection = section; @@ -68,6 +71,7 @@ private SyncedFolderParcelable(Parcel read) { mChargingOnly = read.readInt() != 0; mEnabled = read.readInt() != 0; mSubfolderByDate = read.readInt() != 0; + mType = MediaFolderType.getById(read.readInt()); mAccount = read.readString(); mUploadAction = read.readInt(); mSection = read.readInt(); @@ -83,6 +87,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mChargingOnly ? 1 : 0); dest.writeInt(mEnabled ? 1 : 0); dest.writeInt(mSubfolderByDate ? 1 : 0); + dest.writeInt(mType.getId()); dest.writeString(mAccount); dest.writeInt(mUploadAction); dest.writeInt(mSection); @@ -163,6 +168,14 @@ public void setSubfolderByDate(Boolean mSubfolderByDate) { this.mSubfolderByDate = mSubfolderByDate; } + public MediaFolderType getType() { + return mType; + } + + public void setType(MediaFolderType mType) { + this.mType = mType; + } + public Integer getUploadAction() { return mUploadAction; } diff --git a/src/main/java/com/owncloud/android/services/ShutdownReceiver.java b/src/main/java/com/owncloud/android/ui/events/InitiateSyncedFolder.java similarity index 56% rename from src/main/java/com/owncloud/android/services/ShutdownReceiver.java rename to src/main/java/com/owncloud/android/ui/events/InitiateSyncedFolder.java index 940dd1e45ed5..0526061435f5 100644 --- a/src/main/java/com/owncloud/android/services/ShutdownReceiver.java +++ b/src/main/java/com/owncloud/android/ui/events/InitiateSyncedFolder.java @@ -3,7 +3,6 @@ * * @author Mario Danic * Copyright (C) 2017 Mario Danic - * Copyright (C) 2017 Nextcloud GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -18,23 +17,19 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package com.owncloud.android.services; +package com.owncloud.android.ui.events; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; +import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.MainApp; +public class InitiateSyncedFolder { + private final SyncedFolder syncedFolder; -/** - * Handles shutdown procedure - basically just waits a little bit for all jobs to finish - */ -public class ShutdownReceiver extends BroadcastReceiver { - @Override - public void onReceive(final Context context, final Intent intent) { - if (MainApp.getSyncedFolderObserverService() != null) { - MainApp.getSyncedFolderObserverService().onDestroy(); - } + public InitiateSyncedFolder(SyncedFolder syncedFolder) { + this.syncedFolder = syncedFolder; + } + + public SyncedFolder getSyncedFolder() { + return syncedFolder; } } diff --git a/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java index 50efa6ceca00..26ec97cd2027 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java @@ -955,7 +955,7 @@ public void onConfigurationChanged(Configuration newConfig) { maxColumnSize = maxColumnSizePortrait; } - if (mGridView.getNumColumns() > maxColumnSize) { + if (mGridView != null && mGridView.getNumColumns() > maxColumnSize) { mGridView.setNumColumns(maxColumnSize); mGridView.invalidateViews(); } diff --git a/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java index 675cb6bbe555..4c718e3f8055 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java @@ -26,6 +26,8 @@ import android.os.Environment; import android.util.SparseBooleanArray; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; @@ -89,7 +91,6 @@ public void onAttach(Activity activity) { } } - /** * {@inheritDoc} */ @@ -97,11 +98,19 @@ public void onAttach(Activity activity) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log_OC.i(TAG, "onCreateView() start"); View v = super.onCreateView(inflater, container, savedInstanceState); - setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + + if(!mContainerActivity.isFolderPickerMode()) { + setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + setMessageForEmptyList(R.string.file_list_empty_headline, R.string.local_file_list_empty, + R.drawable.ic_list_empty_folder, true); + } else { + setMessageForEmptyList(R.string.folder_list_empty_headline, R.string.local_folder_list_empty, + R.drawable.ic_list_empty_folder, true); + } + setSwipeEnabled(false); // Disable pull-to-refresh setFabEnabled(false); // Disable FAB - setMessageForEmptyList(R.string.file_list_empty_headline, R.string.local_file_list_empty, - R.drawable.ic_list_empty_folder, true); + Log_OC.i(TAG, "onCreateView() end"); return v; } @@ -115,11 +124,29 @@ public void onActivityCreated(Bundle savedInstanceState) { Log_OC.i(TAG, "onActivityCreated() start"); super.onActivityCreated(savedInstanceState); - mAdapter = new LocalFileListAdapter(mContainerActivity.getInitialDirectory(), getActivity()); + + mAdapter = new LocalFileListAdapter( + mContainerActivity.isFolderPickerMode(), + mContainerActivity.getInitialDirectory(), + getActivity() + ); setListAdapter(mAdapter); Log_OC.i(TAG, "onActivityCreated() stop"); } + + /** + * {@inheritDoc} + */ + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (mContainerActivity.isFolderPickerMode()) { + menu.removeItem(R.id.action_select_all); + menu.removeItem(R.id.action_search); + } else { + super.onCreateOptionsMenu(menu, inflater); + } + } /** * Checks the file clicked over. Browses inside if it is a directory. @@ -303,8 +330,7 @@ public interface ContainerActivity { * @param file */ void onFileClick(File file); - - + /** * Callback method invoked when the parent activity * is fully created to get the directory to list firstly. @@ -313,6 +339,13 @@ public interface ContainerActivity { */ File getInitialDirectory(); + /** + * config check if the list should behave in + * folder picker mode only displaying folders but no files. + * + * @return true if folder picker mode, else false + */ + boolean isFolderPickerMode(); } diff --git a/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java index 2b8c16d150c0..dcda8f6b8420 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java @@ -67,7 +67,7 @@ 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.services.ContactsImportJob; +import com.owncloud.android.jobs.ContactsImportJob; import com.owncloud.android.ui.TextDrawable; import com.owncloud.android.ui.activity.ContactsPreferenceActivity; import com.owncloud.android.ui.events.VCardToggleEvent; diff --git a/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java b/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java index 959d87de91d0..49832fa71dc2 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java @@ -49,9 +49,9 @@ import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.jobs.ContactsBackupJob; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.RefreshFolderOperation; -import com.owncloud.android.services.ContactsBackupJob; import com.owncloud.android.ui.activity.ContactsPreferenceActivity; import com.owncloud.android.ui.activity.Preferences; import com.owncloud.android.ui.fragment.FileFragment; @@ -338,7 +338,7 @@ private void setAutomaticBackup(final boolean bool) { contactsPreferenceActivity.getAccount()); } - arbitraryDataProvider.storeOrUpdateKeyValue(account, PREFERENCE_CONTACTS_AUTOMATIC_BACKUP, + arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PREFERENCE_CONTACTS_AUTOMATIC_BACKUP, String.valueOf(bool)); } diff --git a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index b4ac3ccd71f4..84f7b3e4f1be 100755 --- a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -176,7 +176,7 @@ public void openFile(OCFile file) { openFileWithIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); List launchables = mFileActivity.getPackageManager(). - queryIntentActivities(openFileWithIntent, PackageManager.GET_INTENT_FILTERS); + queryIntentActivities(openFileWithIntent, PackageManager.GET_RESOLVED_FILTER); if (launchables != null && launchables.size() > 0) { try { diff --git a/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java b/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java index 7a5ca7e6ab82..2a829eed2a17 100644 --- a/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java +++ b/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java @@ -166,7 +166,9 @@ private void requestUpload(String localPath, String remotePath) { mBehaviour, null, // MIME type will be detected from file name false, // do not create parent folder if not existent - UploadFileOperation.CREATED_BY_USER + UploadFileOperation.CREATED_BY_USER, + false, + false ); } diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java index 081714205987..bd3575348540 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java @@ -183,7 +183,7 @@ public void onStart() { } private void loadAndShowTextPreview() { - mTextLoadTask = new TextLoadAsyncTask(new WeakReference(mTextPreview)); + mTextLoadTask = new TextLoadAsyncTask(new WeakReference<>(mTextPreview)); mTextLoadTask.execute(getFile().getStoragePath()); } @@ -192,14 +192,12 @@ private void loadAndShowTextPreview() { * Reads the file to preview and shows its contents. Too critical to be anonymous. */ private class TextLoadAsyncTask extends AsyncTask { - private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; private final WeakReference mTextViewReference; private TextLoadAsyncTask(WeakReference textView) { mTextViewReference = textView; } - @Override protected void onPreExecute() { // not used at the moment @@ -454,7 +452,7 @@ private void openFile() { * @return 'True' if the file can be handled by the fragment. */ public static boolean canBePreviewed(OCFile file) { - final List unsupportedTypes = new LinkedList(); + final List unsupportedTypes = new LinkedList<>(); unsupportedTypes.add("text/richtext"); unsupportedTypes.add("text/rtf"); unsupportedTypes.add("text/vnd.abc"); diff --git a/src/main/java/com/owncloud/android/utils/BitmapUtils.java b/src/main/java/com/owncloud/android/utils/BitmapUtils.java index 7726cac3991d..0f97f9e0978d 100644 --- a/src/main/java/com/owncloud/android/utils/BitmapUtils.java +++ b/src/main/java/com/owncloud/android/utils/BitmapUtils.java @@ -1,4 +1,4 @@ -/** +/* * ownCloud Android client application * * @author David A. Velasco @@ -23,7 +23,7 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.Matrix; -import android.media.ExifInterface; +import android.support.media.ExifInterface; import android.support.v4.graphics.drawable.RoundedBitmapDrawable; import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; @@ -48,7 +48,7 @@ public class BitmapUtils { * @param srcPath Absolute path to the file containing the image. * @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels. * @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels. - * @return + * @return decoded bitmap */ public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) { @@ -106,6 +106,23 @@ private static int calculateSampleFactor(Options options, int reqWidth, int reqH return inSampleSize; } + /** + * scales a given bitmap depending on the given size parameters. + * + * @param bitmap the bitmap to be scaled + * @param px the target pixel size + * @param width the width + * @param height the height + * @param max the max(height, width) + * @return the scaled bitmap + */ + public static Bitmap scaleBitmap(Bitmap bitmap, float px, int width, int height, int max) { + float scale = px / max; + int w = Math.round(scale * width); + int h = Math.round(scale * height); + return Bitmap.createScaledBitmap(bitmap, w, h, true); + } + /** * Rotate bitmap according to EXIF orientation. * Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ @@ -172,7 +189,7 @@ else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) { * @param h Hue is specified as degrees in the range 0 - 360. * @param s Saturation is specified as a percentage in the range 1 - 100. * @param l Lumanance is specified as a percentage in the range 1 - 100. - * @paran alpha the alpha value between 0 - 1 + * @param alpha the alpha value between 0 - 1 * adapted from https://svn.codehaus.org/griffon/builders/gfxbuilder/tags/GFXBUILDER_0.2/ * gfxbuilder-core/src/main/com/camick/awt/HSLColor.java */ @@ -200,7 +217,7 @@ public static int[] HSLtoRGB(float h, float s, float l, float alpha) { s /= 100f; l /= 100f; - float q = 0; + float q; if (l < 0.5) { q = l * (1 + s); diff --git a/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/src/main/java/com/owncloud/android/utils/DisplayUtils.java index 4b4024ae7ef5..350e4949894e 100644 --- a/src/main/java/com/owncloud/android/utils/DisplayUtils.java +++ b/src/main/java/com/owncloud/android/utils/DisplayUtils.java @@ -389,6 +389,11 @@ public static Point getScreenSize(Activity caller) { public static SpannableStringBuilder createTextWithSpan(String text, String spanText, StyleSpan style) { SpannableStringBuilder sb = new SpannableStringBuilder(text); int start = text.lastIndexOf(spanText); + + if (start < 0) { + start++; + } + int end = start + spanText.length(); sb.setSpan(style, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE); return sb; diff --git a/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java b/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java new file mode 100644 index 000000000000..c92635153ec9 --- /dev/null +++ b/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java @@ -0,0 +1,329 @@ +/** + * Nextcloud Android client application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * Copyright (C) 2017 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ +package com.owncloud.android.utils; + +import android.accounts.Account; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.support.annotation.RequiresApi; +import android.text.TextUtils; +import android.util.Log; + +import com.evernote.android.job.JobRequest; +import com.owncloud.android.MainApp; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.ArbitraryDataProvider; +import com.owncloud.android.datamodel.FilesystemDataProvider; +import com.owncloud.android.datamodel.MediaFolderType; +import com.owncloud.android.datamodel.SyncedFolder; +import com.owncloud.android.datamodel.SyncedFolderProvider; +import com.owncloud.android.datamodel.UploadsStorageManager; +import com.owncloud.android.db.OCUpload; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.jobs.FilesSyncJob; +import com.owncloud.android.jobs.NContentObserverJob; + +import org.lukhnos.nnio.file.FileVisitResult; +import org.lukhnos.nnio.file.Files; +import org.lukhnos.nnio.file.Path; +import org.lukhnos.nnio.file.Paths; +import org.lukhnos.nnio.file.SimpleFileVisitor; +import org.lukhnos.nnio.file.attribute.BasicFileAttributes; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/* + Various utilities that make auto upload tick + */ +public class FilesSyncHelper { + public static final String TAG = "FileSyncHelper"; + + public static final String GLOBAL = "global"; + public static final String SYNCEDFOLDERINITIATED = "syncedFolderIntitiated_"; + + public static int ContentSyncJobId = 315; + + public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) { + final Context context = MainApp.getAppContext(); + final ContentResolver contentResolver = context.getContentResolver(); + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver); + + Long currentTime = System.currentTimeMillis(); + double currentTimeInSeconds = currentTime / 1000.0; + String currentTimeString = Long.toString((long) currentTimeInSeconds); + + String syncedFolderInitiatedKey = SYNCEDFOLDERINITIATED + syncedFolder.getId(); + boolean dryRun = TextUtils.isEmpty(arbitraryDataProvider.getValue + (GLOBAL, syncedFolderInitiatedKey)); + + if (MediaFolderType.IMAGE == syncedFolder.getType()) { + if (dryRun) { + arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey, + currentTimeString); + } else { + FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI + , syncedFolder); + FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + syncedFolder); + } + + } else if (MediaFolderType.VIDEO == syncedFolder.getType()) { + + if (dryRun) { + arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey, + currentTimeString); + } else { + FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Video.Media.INTERNAL_CONTENT_URI, + syncedFolder); + FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + syncedFolder); + } + + } else { + try { + + if (dryRun) { + arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey, + currentTimeString); + } else { + FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver); + Path path = Paths.get(syncedFolder.getLocalPath()); + + String dateInitiated = arbitraryDataProvider.getValue(GLOBAL, + syncedFolderInitiatedKey); + + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { + + File file = path.toFile(); + if (attrs.lastModifiedTime().toMillis() >= Long.parseLong(dateInitiated) * 1000) { + filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(), + attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder); + } + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + return FileVisitResult.CONTINUE; + } + }); + + } + + } catch (IOException e) { + Log.e(TAG, "Something went wrong while indexing files for auto upload " + e.getLocalizedMessage()); + } + } + } + + public static void insertAllDBEntries(boolean skipCustom) { + final Context context = MainApp.getAppContext(); + final ContentResolver contentResolver = context.getContentResolver(); + SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver); + + for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { + if ((syncedFolder.isEnabled()) && ((MediaFolderType.CUSTOM != syncedFolder.getType()) || !skipCustom)) { + insertAllDBEntriesForSyncedFolder(syncedFolder); + } + } + } + + private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder) { + final Context context = MainApp.getAppContext(); + final ContentResolver contentResolver = context.getContentResolver(); + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver); + + Cursor cursor; + int column_index_data; + int column_index_date_modified; + + final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver); + + String contentPath; + boolean isFolder; + + String[] projection = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DATE_MODIFIED}; + + String path = syncedFolder.getLocalPath(); + if (!path.endsWith("/")) { + path = path + "/%"; + } else { + path = path + "%"; + } + + String syncedFolderInitiatedKey = SYNCEDFOLDERINITIATED + syncedFolder.getId(); + String dateInitiated = arbitraryDataProvider.getValue(GLOBAL, syncedFolderInitiatedKey); + + cursor = context.getContentResolver().query(uri, projection, MediaStore.MediaColumns.DATA + " LIKE ?", + new String[]{path}, null); + + if (cursor != null) { + column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + column_index_date_modified = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED); + while (cursor.moveToNext()) { + contentPath = cursor.getString(column_index_data); + isFolder = new File(contentPath).isDirectory(); + if (cursor.getLong(column_index_date_modified) >= Long.parseLong(dateInitiated)) { + filesystemDataProvider.storeOrUpdateFileValue(contentPath, + cursor.getLong(column_index_date_modified), isFolder, syncedFolder); + } + } + cursor.close(); + } + } + + public static void restartJobsIfNeeded() { + final Context context = MainApp.getAppContext(); + + FileUploader.UploadRequester uploadRequester = new FileUploader.UploadRequester(); + + boolean accountExists; + + UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(context.getContentResolver(), context); + OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads(); + + for (OCUpload failedUpload : failedUploads) { + accountExists = false; + + // check if accounts still exists + for (Account account : AccountUtils.getAccounts(context)) { + if (account.name.equals(failedUpload.getAccountName())) { + accountExists = true; + break; + } + } + + if (!accountExists) { + uploadsStorageManager.removeUpload(failedUpload); + } + } + + uploadRequester.retryFailedUploads( + context, + null, + null + ); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean isContentObserverJobScheduled() { + JobScheduler js = MainApp.getAppContext().getSystemService(JobScheduler.class); + List jobs = js.getAllPendingJobs(); + + if (jobs == null || jobs.size() == 0) { + return false; + } + + for (int i = 0; i < jobs.size(); i++) { + if (jobs.get(i).getId() == ContentSyncJobId) { + return true; + } + } + + return false; + } + + public static void scheduleNJobs(boolean force) { + SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(MainApp.getAppContext(). + getContentResolver()); + + + boolean hasVideoFolders = false; + boolean hasImageFolders = false; + + if (syncedFolderProvider.getSyncedFolders() != null) { + for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) { + if (MediaFolderType.VIDEO == syncedFolder.getType()) { + hasVideoFolders = true; + } else if (MediaFolderType.IMAGE == syncedFolder.getType()) { + hasImageFolders = true; + } + } + } + + if (hasImageFolders || hasVideoFolders) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + scheduleJobOnN(hasImageFolders, hasVideoFolders, force); + } + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + cancelJobOnN(); + } + } + } + + public static void scheduleFilesSyncIfNeeded() { + // always run this because it also allows us to perform retries of manual uploads + new JobRequest.Builder(FilesSyncJob.TAG) + .setPeriodic(900000L, 300000L) + .setUpdateCurrent(true) + .build() + .schedule(); + + scheduleNJobs(false); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private static void cancelJobOnN() { + JobScheduler jobScheduler = MainApp.getAppContext().getSystemService(JobScheduler.class); + if (isContentObserverJobScheduled()) { + jobScheduler.cancel(ContentSyncJobId); + } + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private static void scheduleJobOnN(boolean hasImageFolders, boolean hasVideoFolders, + boolean force) { + JobScheduler jobScheduler = MainApp.getAppContext().getSystemService(JobScheduler.class); + + if ((hasImageFolders || hasVideoFolders) && (!isContentObserverJobScheduled() || force)) { + JobInfo.Builder builder = new JobInfo.Builder(ContentSyncJobId, new ComponentName(MainApp.getAppContext(), + NContentObserverJob.class.getName())); + builder.addTriggerContentUri(new JobInfo.TriggerContentUri(android.provider.MediaStore. + Images.Media.INTERNAL_CONTENT_URI, + JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)); + builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MediaStore. + Images.Media.EXTERNAL_CONTENT_URI, + JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)); + builder.addTriggerContentUri(new JobInfo.TriggerContentUri(android.provider.MediaStore. + Video.Media.INTERNAL_CONTENT_URI, + JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)); + builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MediaStore. + Video.Media.EXTERNAL_CONTENT_URI, + JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)); + builder.setTriggerContentMaxDelay(1500); + jobScheduler.schedule(builder.build()); + } + } +} + diff --git a/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java b/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java index 8984a639593e..b45b9be79176 100644 --- a/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java +++ b/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java @@ -147,7 +147,7 @@ public static Drawable getFolderTypeIcon(boolean isSharedViaUsers, boolean isSha } else if (isSharedViaUsers) { drawableId = R.drawable.shared_with_me_folder; } else { - drawableId = R.drawable.ic_menu_archive; + drawableId = R.drawable.folder; } return ThemeUtils.tintDrawable(drawableId, ThemeUtils.primaryColor(account)); @@ -442,7 +442,7 @@ private static void populateMimeTypeIconMapping() { MIMETYPE_TO_ICON_MAPPING.put("application/yaml", R.drawable.file_code); MIMETYPE_TO_ICON_MAPPING.put("application/zip", R.drawable.file_zip); MIMETYPE_TO_ICON_MAPPING.put("database", R.drawable.file); - MIMETYPE_TO_ICON_MAPPING.put("httpd/unix-directory", R.drawable.ic_menu_archive); + MIMETYPE_TO_ICON_MAPPING.put("httpd/unix-directory", R.drawable.folder); MIMETYPE_TO_ICON_MAPPING.put("image/svg+xml", R.drawable.file_image); MIMETYPE_TO_ICON_MAPPING.put("image/vector", R.drawable.file_image); MIMETYPE_TO_ICON_MAPPING.put("text/calendar", R.drawable.file_calendar); @@ -456,7 +456,7 @@ private static void populateMimeTypeIconMapping() { MIMETYPE_TO_ICON_MAPPING.put("text/x-python", R.drawable.file_code); MIMETYPE_TO_ICON_MAPPING.put("text/x-shellscript", R.drawable.file_code); MIMETYPE_TO_ICON_MAPPING.put("web", R.drawable.file_code); - MIMETYPE_TO_ICON_MAPPING.put(MimeType.DIRECTORY, R.drawable.ic_menu_archive); + MIMETYPE_TO_ICON_MAPPING.put(MimeType.DIRECTORY, R.drawable.folder); } /** diff --git a/src/main/java/com/owncloud/android/utils/ReceiversHelper.java b/src/main/java/com/owncloud/android/utils/ReceiversHelper.java new file mode 100644 index 000000000000..6ee795064626 --- /dev/null +++ b/src/main/java/com/owncloud/android/utils/ReceiversHelper.java @@ -0,0 +1,74 @@ +/** + * Nextcloud Android client application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * Copyright (C) 2017 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ +package com.owncloud.android.utils; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import com.evernote.android.job.JobRequest; +import com.evernote.android.job.util.Device; +import com.owncloud.android.MainApp; + +/* + Helper for setting up network and power receivers + */ +public class ReceiversHelper { + + public static void registerNetworkChangeReceiver() { + Context context = MainApp.getAppContext(); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); + intentFilter.addAction("android.net.wifi.STATE_CHANGE"); + + BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) { + FilesSyncHelper.restartJobsIfNeeded(); + } + } + }; + + context.registerReceiver(broadcastReceiver, intentFilter); + } + + public static void registerPowerChangeReceiver() { + Context context = MainApp.getAppContext(); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_POWER_CONNECTED); + intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED); + + BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)) { + FilesSyncHelper.restartJobsIfNeeded(); + } + } + }; + + context.registerReceiver(broadcastReceiver, intentFilter); + } +} diff --git a/src/main/java/com/owncloud/android/utils/ThemeUtils.java b/src/main/java/com/owncloud/android/utils/ThemeUtils.java index f5ce587e4ee4..e1eef907acee 100644 --- a/src/main/java/com/owncloud/android/utils/ThemeUtils.java +++ b/src/main/java/com/owncloud/android/utils/ThemeUtils.java @@ -37,6 +37,7 @@ import android.support.v4.content.res.ResourcesCompat; import android.support.v4.graphics.ColorUtils; import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v4.widget.CompoundButtonCompat; import android.support.v7.app.ActionBar; import android.support.v7.widget.AppCompatCheckBox; import android.support.v7.widget.SwitchCompat; @@ -254,7 +255,7 @@ public static void colorToolbarProgressBar(FragmentActivity activity, int progre } public static void tintCheckbox(AppCompatCheckBox checkBox, int color) { - checkBox.setSupportButtonTintList(new ColorStateList( + CompoundButtonCompat.setButtonTintList(checkBox, new ColorStateList( new int[][]{ new int[]{-android.R.attr.state_checked}, new int[]{android.R.attr.state_checked}, diff --git a/src/main/java/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java b/src/main/java/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java index e40252c94452..937b66383b57 100644 --- a/src/main/java/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java +++ b/src/main/java/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java @@ -273,7 +273,9 @@ private int getColumnWidthCompatible() { Field numColumns = getClass().getSuperclass().getDeclaredField("mColumnWidth"); numColumns.setAccessible(true); return numColumns.getInt(this); - } catch (NoSuchFieldException | IllegalAccessException e) { + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { throw new RuntimeException(e); } } diff --git a/src/main/res/drawable-hdpi/common_error.png b/src/main/res/drawable-hdpi/common_error.png deleted file mode 100644 index 14590e5b1100..000000000000 Binary files a/src/main/res/drawable-hdpi/common_error.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_menu_archive.png b/src/main/res/drawable-hdpi/folder.png similarity index 100% rename from src/main/res/drawable-hdpi/ic_menu_archive.png rename to src/main/res/drawable-hdpi/folder.png diff --git a/src/main/res/drawable-hdpi/ic_action_cancel_sync.png b/src/main/res/drawable-hdpi/ic_action_cancel_sync.png deleted file mode 100644 index 491fcbca7492..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_cancel_sync.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_cancel_white.png b/src/main/res/drawable-hdpi/ic_action_cancel_white.png deleted file mode 100644 index 0754e9faf57f..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_cancel_white.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_copy.png b/src/main/res/drawable-hdpi/ic_action_copy.png deleted file mode 100644 index 2648a0bd4f1d..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_copy.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_delete_white.png b/src/main/res/drawable-hdpi/ic_action_delete_white.png deleted file mode 100644 index fc4c01df0f2e..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_delete_white.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_download.png b/src/main/res/drawable-hdpi/ic_action_download.png deleted file mode 100644 index d85f3e716bfb..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_download.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_move.png b/src/main/res/drawable-hdpi/ic_action_move.png deleted file mode 100644 index e86a9654395d..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_move.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_set_available_offline.png b/src/main/res/drawable-hdpi/ic_action_set_available_offline.png deleted file mode 100644 index 3b5149208724..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_set_available_offline.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_set_available_offline_white.png b/src/main/res/drawable-hdpi/ic_action_set_available_offline_white.png deleted file mode 100644 index 360c4105b084..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_set_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_share.png b/src/main/res/drawable-hdpi/ic_action_share.png deleted file mode 100644 index 513199dbbbaf..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_share.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_unset_available_offline.png b/src/main/res/drawable-hdpi/ic_action_unset_available_offline.png deleted file mode 100644 index ef6229fcc0a2..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_unset_available_offline.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_action_unset_available_offline_white.png b/src/main/res/drawable-hdpi/ic_action_unset_available_offline_white.png deleted file mode 100644 index 65c3814250c1..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_action_unset_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_cellphone.png b/src/main/res/drawable-hdpi/ic_cellphone.png deleted file mode 100644 index aab56fea7077..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_cellphone.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_edit.png b/src/main/res/drawable-hdpi/ic_edit.png deleted file mode 100644 index 1ca8fa1001ec..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_edit.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_favorite_grey.png b/src/main/res/drawable-hdpi/ic_favorite_grey.png deleted file mode 100644 index eeac324496db..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_favorite_grey.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_folder_open.png b/src/main/res/drawable-hdpi/ic_folder_open.png new file mode 100644 index 000000000000..b5e1e0dc66ef Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_folder_open.png differ diff --git a/src/main/res/drawable-hdpi/ic_folder_star_18dp.png b/src/main/res/drawable-hdpi/ic_folder_star_18dp.png new file mode 100644 index 000000000000..7161e1d2b934 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_folder_star_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_image_18dp.png b/src/main/res/drawable-hdpi/ic_image_18dp.png new file mode 100644 index 000000000000..957495d07a7d Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_image_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_information.png b/src/main/res/drawable-hdpi/ic_information.png deleted file mode 100644 index 92d04d8a0340..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_information.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_key.png b/src/main/res/drawable-hdpi/ic_key.png deleted file mode 100644 index 73bf0c00e0e8..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_key.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_link_black.png b/src/main/res/drawable-hdpi/ic_link_black.png deleted file mode 100644 index 76003e2aa387..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_link_black.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_navigate_next.png b/src/main/res/drawable-hdpi/ic_navigate_next.png deleted file mode 100644 index a4221baf6f48..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_navigate_next.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_open_in_app.png b/src/main/res/drawable-hdpi/ic_open_in_app.png deleted file mode 100644 index 4eb9993eebac..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_open_in_app.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_open_with.png b/src/main/res/drawable-hdpi/ic_open_with.png deleted file mode 100644 index 6650711c0090..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_open_with.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_pencil.png b/src/main/res/drawable-hdpi/ic_pencil.png deleted file mode 100644 index 7c6a745434e3..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_pencil.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_send.png b/src/main/res/drawable-hdpi/ic_send.png deleted file mode 100644 index c53f0b6b63ca..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_send.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_share.png b/src/main/res/drawable-hdpi/ic_share.png deleted file mode 100644 index 827e254c95e1..000000000000 Binary files a/src/main/res/drawable-hdpi/ic_share.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_video_18dp.png b/src/main/res/drawable-hdpi/ic_video_18dp.png new file mode 100644 index 000000000000..484034e2798f Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_video_18dp.png differ diff --git a/src/main/res/drawable-hdpi/nav_folder_sync.png b/src/main/res/drawable-hdpi/nav_synced_folders.png similarity index 100% rename from src/main/res/drawable-hdpi/nav_folder_sync.png rename to src/main/res/drawable-hdpi/nav_synced_folders.png diff --git a/src/main/res/drawable-mdpi/common_error.png b/src/main/res/drawable-mdpi/common_error.png deleted file mode 100644 index ee60165e7284..000000000000 Binary files a/src/main/res/drawable-mdpi/common_error.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_menu_archive.png b/src/main/res/drawable-mdpi/folder.png similarity index 100% rename from src/main/res/drawable-mdpi/ic_menu_archive.png rename to src/main/res/drawable-mdpi/folder.png diff --git a/src/main/res/drawable-mdpi/ic_action_cancel_sync.png b/src/main/res/drawable-mdpi/ic_action_cancel_sync.png deleted file mode 100644 index 36eb8eead40b..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_cancel_sync.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_cancel_white.png b/src/main/res/drawable-mdpi/ic_action_cancel_white.png deleted file mode 100644 index f781daf24c0e..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_cancel_white.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_copy.png b/src/main/res/drawable-mdpi/ic_action_copy.png deleted file mode 100644 index a5abbb5161b8..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_copy.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_delete_white.png b/src/main/res/drawable-mdpi/ic_action_delete_white.png deleted file mode 100644 index e21c04e95dd5..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_delete_white.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_download.png b/src/main/res/drawable-mdpi/ic_action_download.png deleted file mode 100644 index db04fdcdf5bc..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_download.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_move.png b/src/main/res/drawable-mdpi/ic_action_move.png deleted file mode 100644 index a6bac86c7f5c..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_move.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_refresh_grey.png b/src/main/res/drawable-mdpi/ic_action_refresh_grey.png deleted file mode 100644 index 3995cd74b957..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_refresh_grey.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_set_available_offline.png b/src/main/res/drawable-mdpi/ic_action_set_available_offline.png deleted file mode 100644 index 4db968d3b76b..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_set_available_offline.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_set_available_offline_white.png b/src/main/res/drawable-mdpi/ic_action_set_available_offline_white.png deleted file mode 100644 index f82d26500e8d..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_set_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_share.png b/src/main/res/drawable-mdpi/ic_action_share.png deleted file mode 100644 index 7c1deede7c16..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_share.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_unset_available_offline.png b/src/main/res/drawable-mdpi/ic_action_unset_available_offline.png deleted file mode 100644 index 4facb0c88eff..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_unset_available_offline.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_action_unset_available_offline_white.png b/src/main/res/drawable-mdpi/ic_action_unset_available_offline_white.png deleted file mode 100644 index ee0d115bbb27..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_action_unset_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_cellphone.png b/src/main/res/drawable-mdpi/ic_cellphone.png deleted file mode 100644 index 442bdfe6e854..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_cellphone.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_edit.png b/src/main/res/drawable-mdpi/ic_edit.png deleted file mode 100644 index 8c7ec3c17cf4..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_edit.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_favorite_grey.png b/src/main/res/drawable-mdpi/ic_favorite_grey.png deleted file mode 100644 index 46a2f3066bc6..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_favorite_grey.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_folder_open.png b/src/main/res/drawable-mdpi/ic_folder_open.png new file mode 100644 index 000000000000..0a53eea0dfc6 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_folder_open.png differ diff --git a/src/main/res/drawable-mdpi/ic_folder_star_18dp.png b/src/main/res/drawable-mdpi/ic_folder_star_18dp.png new file mode 100644 index 000000000000..1875a1662f36 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_folder_star_18dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_image_18dp.png b/src/main/res/drawable-mdpi/ic_image_18dp.png new file mode 100644 index 000000000000..eb5683299501 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_image_18dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_information.png b/src/main/res/drawable-mdpi/ic_information.png deleted file mode 100644 index f21c1abc765a..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_information.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_key.png b/src/main/res/drawable-mdpi/ic_key.png deleted file mode 100644 index dcf345be5d77..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_key.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_link_black.png b/src/main/res/drawable-mdpi/ic_link_black.png deleted file mode 100644 index 67b2a9eaf226..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_link_black.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_navigate_next.png b/src/main/res/drawable-mdpi/ic_navigate_next.png deleted file mode 100644 index 3c237ed85f00..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_navigate_next.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_open_in_app.png b/src/main/res/drawable-mdpi/ic_open_in_app.png deleted file mode 100644 index bf2b7f8689e4..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_open_in_app.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_open_with.png b/src/main/res/drawable-mdpi/ic_open_with.png deleted file mode 100644 index 128997c0cf9e..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_open_with.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_pencil.png b/src/main/res/drawable-mdpi/ic_pencil.png deleted file mode 100644 index 7c6a745434e3..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_pencil.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_refresh.png b/src/main/res/drawable-mdpi/ic_refresh.png deleted file mode 100644 index f37c0781091c..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_refresh.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_send.png b/src/main/res/drawable-mdpi/ic_send.png deleted file mode 100644 index cfa19a893a08..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_send.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_share.png b/src/main/res/drawable-mdpi/ic_share.png deleted file mode 100644 index 6ba409b162d6..000000000000 Binary files a/src/main/res/drawable-mdpi/ic_share.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_video_18dp.png b/src/main/res/drawable-mdpi/ic_video_18dp.png new file mode 100644 index 000000000000..33f88db487ef Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_video_18dp.png differ diff --git a/src/main/res/drawable-mdpi/nav_folder_sync.png b/src/main/res/drawable-mdpi/nav_synced_folders.png similarity index 100% rename from src/main/res/drawable-mdpi/nav_folder_sync.png rename to src/main/res/drawable-mdpi/nav_synced_folders.png diff --git a/src/main/res/drawable-xhdpi/common_error.png b/src/main/res/drawable-xhdpi/common_error.png deleted file mode 100644 index 08177e7b48d7..000000000000 Binary files a/src/main/res/drawable-xhdpi/common_error.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_menu_archive.png b/src/main/res/drawable-xhdpi/folder.png similarity index 100% rename from src/main/res/drawable-xhdpi/ic_menu_archive.png rename to src/main/res/drawable-xhdpi/folder.png diff --git a/src/main/res/drawable-xhdpi/ic_action_cancel_sync.png b/src/main/res/drawable-xhdpi/ic_action_cancel_sync.png deleted file mode 100644 index 031711c0f1ba..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_cancel_sync.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_cancel_white.png b/src/main/res/drawable-xhdpi/ic_action_cancel_white.png deleted file mode 100644 index 690543be50ac..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_cancel_white.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_copy.png b/src/main/res/drawable-xhdpi/ic_action_copy.png deleted file mode 100644 index afd28ddd4a38..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_copy.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_delete_white.png b/src/main/res/drawable-xhdpi/ic_action_delete_white.png deleted file mode 100644 index 8692fd406c48..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_delete_white.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_download.png b/src/main/res/drawable-xhdpi/ic_action_download.png deleted file mode 100644 index b8bd8a6d87d1..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_download.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_move.png b/src/main/res/drawable-xhdpi/ic_action_move.png deleted file mode 100644 index d231c10e726d..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_move.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_set_available_offline.png b/src/main/res/drawable-xhdpi/ic_action_set_available_offline.png deleted file mode 100644 index 990dfb82b383..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_set_available_offline.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_set_available_offline_white.png b/src/main/res/drawable-xhdpi/ic_action_set_available_offline_white.png deleted file mode 100644 index 17606b3931d5..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_set_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_share.png b/src/main/res/drawable-xhdpi/ic_action_share.png deleted file mode 100644 index 1cafd21351a1..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_share.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_unset_available_offline.png b/src/main/res/drawable-xhdpi/ic_action_unset_available_offline.png deleted file mode 100644 index c920e0296ca8..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_unset_available_offline.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_action_unset_available_offline_white.png b/src/main/res/drawable-xhdpi/ic_action_unset_available_offline_white.png deleted file mode 100644 index 20df979bd8be..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_action_unset_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_cellphone.png b/src/main/res/drawable-xhdpi/ic_cellphone.png deleted file mode 100644 index 4a5f0b29cfad..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_cellphone.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_edit.png b/src/main/res/drawable-xhdpi/ic_edit.png deleted file mode 100644 index 7c6a745434e3..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_edit.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_favorite_grey.png b/src/main/res/drawable-xhdpi/ic_favorite_grey.png deleted file mode 100644 index 1e9eaa4bd5ab..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_favorite_grey.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_folder_open.png b/src/main/res/drawable-xhdpi/ic_folder_open.png new file mode 100644 index 000000000000..ad4fcfa2b5a0 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_folder_open.png differ diff --git a/src/main/res/drawable-xhdpi/ic_folder_star_18dp.png b/src/main/res/drawable-xhdpi/ic_folder_star_18dp.png new file mode 100644 index 000000000000..89b75e492d7d Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_folder_star_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_image_18dp.png b/src/main/res/drawable-xhdpi/ic_image_18dp.png new file mode 100644 index 000000000000..fa93aa3c9adc Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_image_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_information.png b/src/main/res/drawable-xhdpi/ic_information.png deleted file mode 100644 index 122c183906ea..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_information.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_key.png b/src/main/res/drawable-xhdpi/ic_key.png deleted file mode 100644 index 8b44a8181600..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_key.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_link_black.png b/src/main/res/drawable-xhdpi/ic_link_black.png deleted file mode 100644 index 248782d4a7a4..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_link_black.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_navigate_next.png b/src/main/res/drawable-xhdpi/ic_navigate_next.png deleted file mode 100644 index 8eab33c63056..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_navigate_next.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_open_in_app.png b/src/main/res/drawable-xhdpi/ic_open_in_app.png deleted file mode 100644 index bf2b7f8689e4..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_open_in_app.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_open_with.png b/src/main/res/drawable-xhdpi/ic_open_with.png deleted file mode 100644 index bf2b7f8689e4..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_open_with.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_pencil.png b/src/main/res/drawable-xhdpi/ic_pencil.png deleted file mode 100644 index 7c6a745434e3..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_pencil.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_refresh.png b/src/main/res/drawable-xhdpi/ic_refresh.png deleted file mode 100644 index 63f616dc29a3..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_refresh.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_send.png b/src/main/res/drawable-xhdpi/ic_send.png deleted file mode 100644 index cd2590b7b5c2..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_send.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_share.png b/src/main/res/drawable-xhdpi/ic_share.png deleted file mode 100644 index 1cafd21351a1..000000000000 Binary files a/src/main/res/drawable-xhdpi/ic_share.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_video_18dp.png b/src/main/res/drawable-xhdpi/ic_video_18dp.png new file mode 100644 index 000000000000..557c6a7cd266 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_video_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/nav_folder_sync.png b/src/main/res/drawable-xhdpi/nav_synced_folders.png similarity index 100% rename from src/main/res/drawable-xhdpi/nav_folder_sync.png rename to src/main/res/drawable-xhdpi/nav_synced_folders.png diff --git a/src/main/res/drawable-xxhdpi/common_error.png b/src/main/res/drawable-xxhdpi/common_error.png deleted file mode 100644 index dc007ee65fa5..000000000000 Binary files a/src/main/res/drawable-xxhdpi/common_error.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_menu_archive.png b/src/main/res/drawable-xxhdpi/folder.png similarity index 100% rename from src/main/res/drawable-xxhdpi/ic_menu_archive.png rename to src/main/res/drawable-xxhdpi/folder.png diff --git a/src/main/res/drawable-xxhdpi/ic_action_cancel_sync.png b/src/main/res/drawable-xxhdpi/ic_action_cancel_sync.png deleted file mode 100644 index 84a327a8fd4d..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_cancel_sync.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_cancel_white.png b/src/main/res/drawable-xxhdpi/ic_action_cancel_white.png deleted file mode 100644 index f06f53d969e5..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_cancel_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_copy.png b/src/main/res/drawable-xxhdpi/ic_action_copy.png deleted file mode 100644 index c170a270d0c9..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_copy.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_delete_white.png b/src/main/res/drawable-xxhdpi/ic_action_delete_white.png deleted file mode 100644 index 3e6a6ac5591f..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_delete_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_download.png b/src/main/res/drawable-xxhdpi/ic_action_download.png deleted file mode 100644 index 382a7ec896bf..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_download.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_move.png b/src/main/res/drawable-xxhdpi/ic_action_move.png deleted file mode 100644 index 13ab75510c82..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_move.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_set_available_offline.png b/src/main/res/drawable-xxhdpi/ic_action_set_available_offline.png deleted file mode 100644 index 95502de3f1c9..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_set_available_offline.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_set_available_offline_white.png b/src/main/res/drawable-xxhdpi/ic_action_set_available_offline_white.png deleted file mode 100644 index 75f1922678b4..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_set_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_share.png b/src/main/res/drawable-xxhdpi/ic_action_share.png deleted file mode 100644 index 0c460c479db4..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_share.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_unset_available_offline.png b/src/main/res/drawable-xxhdpi/ic_action_unset_available_offline.png deleted file mode 100644 index 4aea83a9ec5f..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_unset_available_offline.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_action_unset_available_offline_white.png b/src/main/res/drawable-xxhdpi/ic_action_unset_available_offline_white.png deleted file mode 100644 index f6ebd03db120..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_action_unset_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_cellphone.png b/src/main/res/drawable-xxhdpi/ic_cellphone.png deleted file mode 100644 index 393e0e3fa5da..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_cellphone.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_edit.png b/src/main/res/drawable-xxhdpi/ic_edit.png deleted file mode 100644 index 2beb2b4279e7..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_edit.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_favorite_grey.png b/src/main/res/drawable-xxhdpi/ic_favorite_grey.png deleted file mode 100644 index 26a843ba57e5..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_favorite_grey.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_folder_open.png b/src/main/res/drawable-xxhdpi/ic_folder_open.png new file mode 100644 index 000000000000..bc826f4ab947 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_folder_open.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_folder_star_18dp.png b/src/main/res/drawable-xxhdpi/ic_folder_star_18dp.png new file mode 100644 index 000000000000..6662bf1572af Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_folder_star_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_image_18dp.png b/src/main/res/drawable-xxhdpi/ic_image_18dp.png new file mode 100644 index 000000000000..b3e3c2ffffdd Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_image_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_information.png b/src/main/res/drawable-xxhdpi/ic_information.png deleted file mode 100644 index e0056bb7fc2d..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_information.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_key.png b/src/main/res/drawable-xxhdpi/ic_key.png deleted file mode 100644 index 27f889e984cd..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_key.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_link_black.png b/src/main/res/drawable-xxhdpi/ic_link_black.png deleted file mode 100644 index af03b8558fde..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_link_black.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_navigate_next.png b/src/main/res/drawable-xxhdpi/ic_navigate_next.png deleted file mode 100644 index d4b141b9da40..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_navigate_next.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_open_with.png b/src/main/res/drawable-xxhdpi/ic_open_with.png deleted file mode 100644 index 455423da7a03..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_open_with.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_pencil.png b/src/main/res/drawable-xxhdpi/ic_pencil.png deleted file mode 100644 index 2beb2b4279e7..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_pencil.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_refresh.png b/src/main/res/drawable-xxhdpi/ic_refresh.png deleted file mode 100644 index 07a6372dfe82..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_refresh.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_send.png b/src/main/res/drawable-xxhdpi/ic_send.png deleted file mode 100644 index 55f6ddb7737e..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_send.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_share.png b/src/main/res/drawable-xxhdpi/ic_share.png deleted file mode 100644 index 0c460c479db4..000000000000 Binary files a/src/main/res/drawable-xxhdpi/ic_share.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_video_18dp.png b/src/main/res/drawable-xxhdpi/ic_video_18dp.png new file mode 100644 index 000000000000..cf610376dbae Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_video_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/nav_folder_sync.png b/src/main/res/drawable-xxhdpi/nav_synced_folders.png similarity index 100% rename from src/main/res/drawable-xxhdpi/nav_folder_sync.png rename to src/main/res/drawable-xxhdpi/nav_synced_folders.png diff --git a/src/main/res/drawable-xxhdpi/owncloud_progress_bg_light.9.png b/src/main/res/drawable-xxhdpi/owncloud_progress_bg_light.9.png deleted file mode 100644 index fb146c33987e..000000000000 Binary files a/src/main/res/drawable-xxhdpi/owncloud_progress_bg_light.9.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/owncloud_progress_primary_light.9.png b/src/main/res/drawable-xxhdpi/owncloud_progress_primary_light.9.png deleted file mode 100644 index 1fc3b4ec7c98..000000000000 Binary files a/src/main/res/drawable-xxhdpi/owncloud_progress_primary_light.9.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/owncloud_progress_secondary_light.9.png b/src/main/res/drawable-xxhdpi/owncloud_progress_secondary_light.9.png deleted file mode 100644 index 6cae209f42bc..000000000000 Binary files a/src/main/res/drawable-xxhdpi/owncloud_progress_secondary_light.9.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_action_cancel_sync.png b/src/main/res/drawable-xxxhdpi/ic_action_cancel_sync.png deleted file mode 100644 index 6db45391d787..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_action_cancel_sync.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_action_delete_white.png b/src/main/res/drawable-xxxhdpi/ic_action_delete_white.png deleted file mode 100644 index 99330656f093..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_action_delete_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_action_download.png b/src/main/res/drawable-xxxhdpi/ic_action_download.png deleted file mode 100644 index 4404433b4756..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_action_download.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_action_set_available_offline_white.png b/src/main/res/drawable-xxxhdpi/ic_action_set_available_offline_white.png deleted file mode 100644 index 23c4b1004b47..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_action_set_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_action_unset_available_offline_white.png b/src/main/res/drawable-xxxhdpi/ic_action_unset_available_offline_white.png deleted file mode 100644 index df89de764099..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_action_unset_available_offline_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_cellphone.png b/src/main/res/drawable-xxxhdpi/ic_cellphone.png deleted file mode 100644 index ebb939ffe1e2..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_cellphone.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_edit.png b/src/main/res/drawable-xxxhdpi/ic_edit.png deleted file mode 100644 index 0a20a6076541..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_edit.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_favorite_grey.png b/src/main/res/drawable-xxxhdpi/ic_favorite_grey.png deleted file mode 100644 index 800daea16723..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_favorite_grey.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_folder_star_18dp.png b/src/main/res/drawable-xxxhdpi/ic_folder_star_18dp.png new file mode 100644 index 000000000000..fbeb81a7ec19 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_folder_star_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_image_18dp.png b/src/main/res/drawable-xxxhdpi/ic_image_18dp.png new file mode 100644 index 000000000000..07f0dfdf5f0b Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_image_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_information.png b/src/main/res/drawable-xxxhdpi/ic_information.png deleted file mode 100644 index 2f932ef8540d..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_information.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_link_black.png b/src/main/res/drawable-xxxhdpi/ic_link_black.png deleted file mode 100644 index 377cf9ab52ed..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_link_black.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_navigate_next.png b/src/main/res/drawable-xxxhdpi/ic_navigate_next.png deleted file mode 100644 index 12ec7bf0ab9e..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_navigate_next.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_open_in_app.png b/src/main/res/drawable-xxxhdpi/ic_open_in_app.png deleted file mode 100644 index 87a63fe252c0..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_open_in_app.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_open_with.png b/src/main/res/drawable-xxxhdpi/ic_open_with.png deleted file mode 100644 index 87a63fe252c0..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_open_with.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_refresh.png b/src/main/res/drawable-xxxhdpi/ic_refresh.png deleted file mode 100644 index c1ea1e23d0cd..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_refresh.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_send.png b/src/main/res/drawable-xxxhdpi/ic_send.png deleted file mode 100644 index c7362e87bcf9..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_send.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_share.png b/src/main/res/drawable-xxxhdpi/ic_share.png deleted file mode 100644 index e275d938448c..000000000000 Binary files a/src/main/res/drawable-xxxhdpi/ic_share.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_video_18dp.png b/src/main/res/drawable-xxxhdpi/ic_video_18dp.png new file mode 100644 index 000000000000..5b3a9e8e4155 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_video_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/nav_folder_sync.png b/src/main/res/drawable-xxxhdpi/nav_synced_folders.png similarity index 100% rename from src/main/res/drawable-xxxhdpi/nav_folder_sync.png rename to src/main/res/drawable-xxxhdpi/nav_synced_folders.png diff --git a/src/main/res/drawable/borderless_btn.xml b/src/main/res/drawable/borderless_btn.xml new file mode 100644 index 000000000000..6cc185ea0f57 --- /dev/null +++ b/src/main/res/drawable/borderless_btn.xml @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/favorite_button_selector.xml b/src/main/res/drawable/favorite_button_selector.xml deleted file mode 100644 index 3868e80cca5f..000000000000 --- a/src/main/res/drawable/favorite_button_selector.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/main/res/layout-land/account_setup.xml b/src/main/res/layout-land/account_setup.xml index 3243dea13d56..224556826651 100644 --- a/src/main/res/layout-land/account_setup.xml +++ b/src/main/res/layout-land/account_setup.xml @@ -145,6 +145,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="@dimen/alternate_half_margin" android:drawableLeft="@android:drawable/stat_notify_sync" + android:drawableStart="@android:drawable/stat_notify_sync" android:drawablePadding="@dimen/alternate_half_padding" android:gravity="center_vertical" android:textColor="@color/login_text_color" @@ -220,8 +221,7 @@ android:hint="@string/auth_username" android:inputType="textNoSuggestions" android:textColor="@color/login_text_color" - android:textColorHint="@color/login_text_hint_color" - android:contentDescription="@string/auth_username"/> + android:textColorHint="@color/login_text_hint_color"/> @@ -253,6 +253,7 @@ android:gravity="center_vertical" android:text="@string/auth_unauthorized" android:drawableLeft="@android:drawable/stat_notify_sync" + android:drawableStart="@android:drawable/stat_notify_sync" android:drawablePadding="@dimen/alternate_half_padding" android:textColor="@color/login_text_color" android:contentDescription="@string/auth_unauthorized" diff --git a/src/main/res/layout/account_action.xml b/src/main/res/layout/account_action.xml index 0bdecc04a645..cb32971336f1 100644 --- a/src/main/res/layout/account_action.xml +++ b/src/main/res/layout/account_action.xml @@ -32,17 +32,20 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="@dimen/list_item_avatar_icon_margin" - android:src="@drawable/ic_account_plus"/> + android:layout_marginStart="@dimen/list_item_avatar_text_margin" + android:src="@drawable/ic_account_plus" + android:contentDescription="@string/prefs_add_account"/> diff --git a/src/main/res/layout/account_item.xml b/src/main/res/layout/account_item.xml index 8b92fe0119a2..5424e5f4cc4e 100644 --- a/src/main/res/layout/account_item.xml +++ b/src/main/res/layout/account_item.xml @@ -43,7 +43,8 @@ android:layout_marginLeft="12dp" android:layout_marginRight="1dp" android:layout_marginTop="1dp" - android:src="@drawable/ic_menu_archive"/> + android:src="@drawable/folder" + android:contentDescription="@string/avatar"/> + android:src="@drawable/ic_account_circle_white_18dp" + android:contentDescription="@string/active_user"/> + android:text="@string/placeholder_filename"/> \ No newline at end of file diff --git a/src/main/res/layout/activity_row.xml b/src/main/res/layout/activity_row.xml index f25e5a0bddd0..ea8eabf03586 100644 --- a/src/main/res/layout/activity_row.xml +++ b/src/main/res/layout/activity_row.xml @@ -50,7 +50,7 @@ android:layout_gravity="center_vertical" android:textAppearance="?android:attr/textAppearanceLargePopupMenu" android:duplicateParentState="true" - android:maxLines="1" + android:singleLine="true" android:ellipsize="marquee" android:fadingEdge="horizontal" /> diff --git a/src/main/res/layout/contactlist_fragment.xml b/src/main/res/layout/contactlist_fragment.xml index 22e10e931c34..2e9449d20a09 100644 --- a/src/main/res/layout/contactlist_fragment.xml +++ b/src/main/res/layout/contactlist_fragment.xml @@ -46,7 +46,8 @@ + android:src="@drawable/uploader_list_separator" + android:contentDescription="@null"/> \ No newline at end of file diff --git a/src/main/res/layout/drawer_header.xml b/src/main/res/layout/drawer_header.xml index 1592e67ba4fe..95b7f5e447c2 100644 --- a/src/main/res/layout/drawer_header.xml +++ b/src/main/res/layout/drawer_header.xml @@ -72,7 +72,7 @@ > diff --git a/src/main/res/layout/file_details_fragment.xml b/src/main/res/layout/file_details_fragment.xml index cffe9ae5d6e7..ca6c9611a3ec 100644 --- a/src/main/res/layout/file_details_fragment.xml +++ b/src/main/res/layout/file_details_fragment.xml @@ -167,7 +167,10 @@ android:layout_height="wrap_content" android:layout_gravity="start" android:text="@string/favorite" - android:textSize="16sp"/> + android:textSize="16sp" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true"/> + android:src="@drawable/ic_cancel" + android:contentDescription="@string/common_cancel"/> diff --git a/src/main/res/layout/file_details_share_user_item.xml b/src/main/res/layout/file_details_share_user_item.xml index 9c7fed9ca2d4..16f59de85554 100644 --- a/src/main/res/layout/file_details_share_user_item.xml +++ b/src/main/res/layout/file_details_share_user_item.xml @@ -35,7 +35,7 @@ android:text="@string/username" android:id="@+id/userOrGroupName" android:layout_margin="12dp" - android:maxLines="1" + android:singleLine="true" android:ellipsize="middle" android:textColor="@color/black"/> diff --git a/src/main/res/layout/file_download_fragment.xml b/src/main/res/layout/file_download_fragment.xml index 28d1846ddad1..9d5fc86c6155 100644 --- a/src/main/res/layout/file_download_fragment.xml +++ b/src/main/res/layout/file_download_fragment.xml @@ -42,27 +42,25 @@ android:gravity="center" android:layout_marginTop="@dimen/fragment_margin" android:layout_marginBottom="@dimen/alternate_fragment_margin" - android:orientation="horizontal" - > + android:orientation="horizontal"> + android:indeterminateOnly="false"/> + android:contentDescription="@string/common_cancel"/> diff --git a/src/main/res/layout/files.xml b/src/main/res/layout/files.xml index 197d14390e41..7af8dfc98115 100644 --- a/src/main/res/layout/files.xml +++ b/src/main/res/layout/files.xml @@ -34,7 +34,6 @@ layout="@layout/toolbar_standard" /> @@ -38,7 +38,8 @@ + android:src="@drawable/uploader_list_separator" + android:contentDescription="@null"/> diff --git a/src/main/res/layout/folder_sync_item_header.xml b/src/main/res/layout/folder_sync_item_header.xml deleted file mode 100644 index a72bdc03fc05..000000000000 --- a/src/main/res/layout/folder_sync_item_header.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/res/layout/grid_image.xml b/src/main/res/layout/grid_image.xml index 6c13b9962c71..3e58eb56be10 100644 --- a/src/main/res/layout/grid_image.xml +++ b/src/main/res/layout/grid_image.xml @@ -36,7 +36,7 @@ android:paddingLeft="@dimen/alternate_padding" android:paddingRight="@dimen/alternate_padding" android:scaleType="centerCrop" - android:src="@drawable/ic_menu_archive"/> + android:src="@drawable/folder"/> - + - + android:layout_height="match_parent" + android:scaleType="centerCrop" + android:src="@drawable/folder"/> - + - + - - - - - - - + android:text="@string/synced_folders_plus" + android:textColor="#ffffff" + android:textSize="22sp" + android:textStyle="bold"/> - \ No newline at end of file + + + \ No newline at end of file diff --git a/src/main/res/layout/list_item.xml b/src/main/res/layout/list_item.xml index 43d3f897d3c2..d1322222b2bd 100644 --- a/src/main/res/layout/list_item.xml +++ b/src/main/res/layout/list_item.xml @@ -44,7 +44,7 @@ android:layout_height="@dimen/file_icon_size" android:layout_centerInParent="true" android:layout_marginLeft="8dp" - android:src="@drawable/ic_menu_archive" /> + android:src="@drawable/folder" /> @@ -110,7 +110,7 @@ android:id="@+id/file_size" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Size MB" + android:text="@string/placeholder_filesize" android:textColor="@color/list_item_lastmod_and_filesize_text" android:textSize="@dimen/two_line_secondary_text_size"/> @@ -118,8 +118,9 @@ android:id="@+id/file_separator" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingRight="@dimen/standard_quarter_padding" android:gravity="right" - android:text=", " + android:text="@string/info_separator" android:textColor="@color/list_item_lastmod_and_filesize_text" android:textSize="@dimen/two_line_secondary_text_size"/> @@ -128,7 +129,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="right" - android:text="Mod Date" + android:text="@string/placeholder_media_time" android:textColor="@color/list_item_lastmod_and_filesize_text" android:textSize="@dimen/two_line_secondary_text_size"/> diff --git a/src/main/res/layout/listrow_details.xml b/src/main/res/layout/listrow_details.xml deleted file mode 100644 index 46e371ebabea..000000000000 --- a/src/main/res/layout/listrow_details.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/res/layout/listrow_group.xml b/src/main/res/layout/listrow_group.xml deleted file mode 100644 index ccea6301cd7d..000000000000 --- a/src/main/res/layout/listrow_group.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/res/layout/log_item.xml b/src/main/res/layout/log_item.xml deleted file mode 100644 index e1083aefda53..000000000000 --- a/src/main/res/layout/log_item.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/main/res/layout/notification_with_progress_bar.xml b/src/main/res/layout/notification_with_progress_bar.xml index 7d7dd8383db0..79588e6e8961 100644 --- a/src/main/res/layout/notification_with_progress_bar.xml +++ b/src/main/res/layout/notification_with_progress_bar.xml @@ -36,7 +36,7 @@ diff --git a/src/main/res/layout/share_user_item.xml b/src/main/res/layout/share_user_item.xml index 5c579f6c19bb..e2eaa866a92e 100644 --- a/src/main/res/layout/share_user_item.xml +++ b/src/main/res/layout/share_user_item.xml @@ -34,7 +34,7 @@ android:layout_gravity="center_vertical"/> @@ -54,7 +55,8 @@ android:id="@+id/unshareButton" android:src="@drawable/ic_action_delete_grey" android:layout_gravity="center_vertical" - android:padding="@dimen/standard_half_padding"/> + android:padding="@dimen/standard_half_padding" + android:contentDescription="@string/common_delete"/> + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/folder_sync_layout.xml b/src/main/res/layout/synced_folders_layout.xml similarity index 50% rename from src/main/res/layout/folder_sync_layout.xml rename to src/main/res/layout/synced_folders_layout.xml index 94fb76abaa43..e7d6c75c6b31 100644 --- a/src/main/res/layout/folder_sync_layout.xml +++ b/src/main/res/layout/synced_folders_layout.xml @@ -2,8 +2,8 @@ - + android:fitsSystemWindows="true" + tools:openDrawer="start"> - - + + + - + - + + + + + + + + + + + + - - + android:orientation="vertical" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> @@ -75,7 +108,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="@dimen/standard_half_margin" - android:text="@string/folder_sync_loading_folders" + android:text="@string/synced_folders_loading_folders" android:textSize="26sp"/> @@ -86,9 +119,21 @@ android:layout_gravity="center" android:layout_margin="@dimen/standard_margin" android:gravity="center" - android:text="@string/folder_sync_no_results" - android:visibility="gone" /> - + android:text="@string/synced_folders_no_results" + android:visibility="gone"/> + + + - + diff --git a/src/main/res/layout/folder_sync_settings_layout.xml b/src/main/res/layout/synced_folders_settings_layout.xml similarity index 65% rename from src/main/res/layout/folder_sync_settings_layout.xml rename to src/main/res/layout/synced_folders_settings_layout.xml index a709bcef1dbd..133c70fa407d 100644 --- a/src/main/res/layout/folder_sync_settings_layout.xml +++ b/src/main/res/layout/synced_folders_settings_layout.xml @@ -18,8 +18,8 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . --> - + android:paddingTop="@dimen/standard_padding"> @@ -62,8 +61,8 @@ android:layout_height="match_parent" android:gravity="end|top" android:orientation="vertical" - android:paddingTop="@dimen/standard_padding" - android:paddingLeft="@dimen/standard_padding"> + android:paddingLeft="@dimen/standard_padding" + android:paddingTop="@dimen/standard_padding"> + android:minHeight="?attr/listPreferredItemHeightSmall"> - + android:layout_weight="1" + android:paddingBottom="@dimen/standard_padding" + android:paddingTop="@dimen/standard_padding"> - + + + + + + + + + + + + + + + + + + android:layout_weight="1" + android:paddingBottom="@dimen/standard_padding" + android:paddingTop="@dimen/standard_padding"> + + + + + + + + + + + + + @@ -139,7 +224,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="marquee" - android:maxLines="1" + android:singleLine="true" android:text="@string/auto_upload_on_wifi" android:textAppearance="?attr/textAppearanceListItem"/> @@ -186,7 +271,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="marquee" - android:maxLines="1" + android:singleLine="true" android:text="@string/instant_upload_on_charging" android:textAppearance="?attr/textAppearanceListItem"/> @@ -233,7 +318,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="marquee" - android:maxLines="1" + android:singleLine="true" android:text="@string/prefs_instant_upload_path_use_subfolders_title" android:textAppearance="?attr/textAppearanceListItem"/> @@ -288,7 +373,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="marquee" - android:maxLines="1" + android:singleLine="true" android:text="@string/prefs_instant_behaviour_title" android:textAppearance="?attr/textAppearanceListItem"/> @@ -307,25 +392,39 @@ - + android:layout_height="wrap_content"> + android:layout_alignParentLeft="true" + android:text="@string/common_delete"/> - + android:layout_alignParentRight="true"> - + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/upload_files_layout.xml b/src/main/res/layout/upload_files_layout.xml index b0b32982cf36..fb74bc9b7ddb 100644 --- a/src/main/res/layout/upload_files_layout.xml +++ b/src/main/res/layout/upload_files_layout.xml @@ -45,11 +45,12 @@ @@ -73,9 +74,7 @@ android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" - android:paddingLeft="@dimen/standard_padding" - android:paddingRight="@dimen/standard_padding" - android:paddingBottom="@dimen/standard_padding"> + android:padding="@dimen/standard_padding"> + android:src="@drawable/folder" /> @@ -37,7 +37,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:ellipsize="middle" - android:maxLines="1" + android:singleLine="true" android:textColor="@color/textColor" android:text="@string/placeholder_filename" android:textSize="@dimen/two_line_primary_text_size" /> @@ -53,7 +53,7 @@ android:gravity="left" android:textColor="@color/list_item_lastmod_and_filesize_text" android:ellipsize="middle" - android:maxLines="1" + android:singleLine="true" android:text="@string/placeholder_filesize" android:textSize="@dimen/upload_list_item_text_size"/> diff --git a/src/main/res/layout/uploader_list_item_layout.xml b/src/main/res/layout/uploader_list_item_layout.xml index 43c68211835e..4a07d82c3b52 100644 --- a/src/main/res/layout/uploader_list_item_layout.xml +++ b/src/main/res/layout/uploader_list_item_layout.xml @@ -29,7 +29,7 @@ android:layout_height="@dimen/file_icon_size" android:layout_gravity="center_vertical" android:layout_margin="@dimen/uploader_list_item_layout_image_margin" - android:src="@drawable/ic_menu_archive" /> + android:src="@drawable/folder" /> @@ -62,7 +62,7 @@ android:id="@+id/file_size" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Size MB" + android:text="@string/placeholder_filesize" android:textColor="@color/list_item_lastmod_and_filesize_text" android:textSize="@dimen/two_line_secondary_text_size"/> @@ -70,8 +70,9 @@ android:id="@+id/file_separator" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingRight="@dimen/standard_quarter_padding" android:gravity="right" - android:text=", " + android:text="@string/info_separator" android:textColor="@color/list_item_lastmod_and_filesize_text" android:textSize="@dimen/two_line_secondary_text_size"/> @@ -80,7 +81,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="right" - android:text="Mod Date" + android:text="@string/placeholder_media_time" android:textColor="@color/list_item_lastmod_and_filesize_text" android:textSize="@dimen/two_line_secondary_text_size"/> diff --git a/src/main/res/menu/drawer_menu.xml b/src/main/res/menu/drawer_menu.xml index 764a14a4d9c1..b84563e4792c 100644 --- a/src/main/res/menu/drawer_menu.xml +++ b/src/main/res/menu/drawer_menu.xml @@ -80,9 +80,9 @@ android:title="@string/drawer_item_notifications"/> + android:id="@+id/nav_synced_folders" + android:icon="@drawable/nav_synced_folders" + android:title="@string/drawer_synced_folders"/> @@ -141,7 +127,6 @@ diff --git a/src/main/res/menu/upload_list_menu.xml b/src/main/res/menu/upload_list_menu.xml index d656e327e338..f20d91f27619 100755 --- a/src/main/res/menu/upload_list_menu.xml +++ b/src/main/res/menu/upload_list_menu.xml @@ -17,18 +17,31 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . --> -

+ + - - - - + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index df339bfda29a..1e3a804f1319 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -262,8 +262,6 @@ نعتذر عن ذلك ! معاينة الصورة %1$s تعذر نسخه %2$s للمجلد المحلي - المجلد المحلي - مجلد عن بعد حدث خطأ ما أثناء محاولة مشاركة هذا الملف أو المجلد حدث خطأ ما أثناء محاولة إلغاء مشاركة هذا الملف أو المجلد إدخِل كلمة السر @@ -340,10 +338,8 @@ هل ترغب فعلاً في حذف العناصر المحددة ؟ بحث تعلم المزيد - رفع تلقائي شارك معنا ساهم بفعالية - الإعدادات لا توجد هناك إخطارات اسم الملف نوع الملف @@ -385,7 +381,5 @@ الخصوصية المجلد غير موجود ! - تهيئة المجلدات - أفحص أتصال الخادم diff --git a/src/main/res/values-ast/strings.xml b/src/main/res/values-ast/strings.xml index 241219c9c8fc..0e6da2ef3c9f 100644 --- a/src/main/res/values-ast/strings.xml +++ b/src/main/res/values-ast/strings.xml @@ -310,8 +310,8 @@ Nun se pudo copiar %1$s al ficheru llocal %2$s Carpeta de xuba nel intre - Carpeta llocal - Carpeta remota + Carpeta llocal + Carpeta remota Usar socarpetes Nun pue compartise. Por favor, comprueba si\'l ficheru esiste Hebo un fallu mientres s\'intentaba compartir esti ficheru o carpeta @@ -468,10 +468,10 @@ Mover a… Copiar a… Escoyer carpeta… - Cargando carpetes… - Nun s\'alcontraron carpetes de medios. - Preferencies pa la xuba automática - Axustes + Cargando carpetes… + Nun s\'alcontraron carpetes de medios. + Preferencies pa la xuba automática + Axustes Cargando actividaes… Nun s\'alcontraron actividaes. diff --git a/src/main/res/values-bg-rBG/strings.xml b/src/main/res/values-bg-rBG/strings.xml index 1734266efa27..f860d2b65086 100644 --- a/src/main/res/values-bg-rBG/strings.xml +++ b/src/main/res/values-bg-rBG/strings.xml @@ -281,8 +281,8 @@ Преглед на изображението %1$s не може да бъде копиран в локалната папка %2$s Папка за незабавно качените - Локална папка - Отдалечена папка + Локална папка + Отдалечена папка Ползване на подпапки Съхраняване в подпапки, разделени по година и месец @@ -416,18 +416,18 @@ Търсене Това е функционалност на Nextcloud, моля актуализирайте. Научете повече - Автоматично качване + Автоматично качване Участвайте Помогни с тестването Интересувате ли се да ни помогнете с тестването на следващата версия? Преместване в… Копиране в… Избери папка… - Зареждане на папки… - Не са открити медийни папки. - Предпочитания за автоматично качване - Настройки - За %1$s + Зареждане на папки… + Не са открити медийни папки. + Предпочитания за автоматично качване + Настройки + За %1$s %d избран %d избрани diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index a193904dc0e0..c346840aadd6 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -275,8 +275,8 @@ Visualització prèvia d\'imatge %1$s no s\'ha pogut copiar a la carpeta local %2$s Carpeta de pujada instantània - Carpeta local - Carpeta remota + Carpeta local + Carpeta remota Feu servir subcarpetes Desar a subcarpetes ordenades per any i mes @@ -415,17 +415,19 @@ Cerca És una característica de Nextcloud. Siusplau actualitzeu. Més informació - Pujada automàtica + Pujada automàtica Participa Versió candidata Contribuïu activament Mou a… Copia a… Tria carpeta… - Carregant carpetes… - No s\'han trobat carpetes amb elements multimèdia. - Configuració - Per %1$s + Carregant carpetes… + No s\'han trobat carpetes amb elements multimèdia. + Preferències de la pujada automàtica + Configuració + La pujada instantània s\'ha renovat completament. Mireu el menú principal i torneu a configurar l\'auto pujada. Disculpeu les molèsties.\n\nGaudiu de les noves i ampliades capacitats de l\'auto pujada! + Per %1$s %d seleccionat %d seleccionats diff --git a/src/main/res/values-cs-rCZ/strings.xml b/src/main/res/values-cs-rCZ/strings.xml index 9871c1e1e38a..68c1a1e63064 100644 --- a/src/main/res/values-cs-rCZ/strings.xml +++ b/src/main/res/values-cs-rCZ/strings.xml @@ -362,8 +362,8 @@ %1$s nelze zkopírovat do místního adresáře %2$s Adresář pro okamžité nahrání - Místní adresář - Vzdálený adresář + Místní adresář + Vzdálený adresář Používat podadresáře Ukládat v podadresářích podle roku a měsíce @@ -557,7 +557,7 @@ Hledat Toto je Nextcloud funkce, prosím nahrát Více - Automatické nahrávání + Automatické nahrávání Zúčastnit se Pomoz nám testovat Našli jste chybu? Něco podivného? @@ -577,12 +577,12 @@ Přesunout do… Kopírovat do… Vybrat adresář… - Načítání adresářů… - Nebyly nalezeny žádné adresáře médií. - Nastavení automatického nahrávání - Nastavení - Okamžité nahrávání dat bylo kompletně předěláno. Navštivte hlavní menu a nakonfigurujte automatické nahrávání svých dat. Omlouváme se za nepříjemnosti.\n\nUžívejte si nové a rozšířené možnosti nahrávání dat! - Pro %1$s + Načítání adresářů… + Nebyly nalezeny žádné adresáře médií. + Nastavení automatického nahrávání + Nastavení + Okamžité nahrávání dat bylo kompletně předěláno. Navštivte hlavní menu a nakonfigurujte automatické nahrávání svých dat. Omlouváme se za nepříjemnosti.\n\nUžívejte si nové a rozšířené možnosti nahrávání dat! + Pro %1$s vybráno %d vybráno %d @@ -666,7 +666,7 @@ Soukromí Soubor nenalezen! - Nastavení adresářů + Nastavení adresářů Otestovat připojení k serveru diff --git a/src/main/res/values-da/strings.xml b/src/main/res/values-da/strings.xml index 7ccf79a53bcb..e3b9bec0d477 100644 --- a/src/main/res/values-da/strings.xml +++ b/src/main/res/values-da/strings.xml @@ -273,8 +273,8 @@ Forhåndsvisning af billede %1$s kunne ikke kopieres til %2$s lokale mappe Øjeblikkelig opload mappe - Lokal mappe - Ekstern mappe + Lokal mappe + Ekstern mappe Benyt undermapper Lagre i undermapper baseret på år og måned @@ -391,15 +391,15 @@ Søg Dette er en Nextcloud feature, opdater venligst Lær mere - Auto upload + Auto upload Deltag Release candidate Flyt til… Kopiér til… Vælg mappe… - Henter mapper… - Ingen medie mappe fundet - Indstillinger + Henter mapper… + Ingen medie mappe fundet + Indstillinger Filnavn Filtype Standard diff --git a/src/main/res/values-de-rDE/strings.xml b/src/main/res/values-de-rDE/strings.xml index cacbc7497103..5e0a1ce40082 100644 --- a/src/main/res/values-de-rDE/strings.xml +++ b/src/main/res/values-de-rDE/strings.xml @@ -363,8 +363,8 @@ %1$s konnte nicht in den lokalen %2$s Ordner kopiert werden Sofort-Upload-Ordner - Lokales Verzeichnis - Entferntes Verzeichnis + Lokales Verzeichnis + Entferntes Verzeichnis Unterordner benutzen In Unterordnern speichern, basierend auf Jahr und Monat @@ -558,7 +558,7 @@ Suche Dies ist eine Nextcloud Funktion, bitte updaten. Mehr - Automatisches Hochladen + Automatisches Hochladen Mitmachen Hilf uns Testen Fehler gefunden? Komisches Verhalten? @@ -578,12 +578,12 @@ Verschieben nach… Kopieren nach… Wähle Verzeichnis… - Lade Verzeichnisse… - Keine Medienverzeichnisse gefunden. - Einstellungen Auto-Hochladen - Einstellungen - Sofortiger Upload wurde vollständig überarbeitet. Bitte über das Hauptmenü zum Auto Upload gehen und neu configurieren. Bitte entschuldige die Unannehmlichkeiten.\n\nViel Vergnügen mit den neuen und erweiterten Möglichkeiten des Auto Uploads. - Für %1$s + Lade Verzeichnisse… + Keine Medienverzeichnisse gefunden. + Einstellungen Auto-Hochladen + Einstellungen + Sofortiger Upload wurde vollständig überarbeitet. Bitte über das Hauptmenü zum Auto Upload gehen und neu configurieren. Bitte entschuldige die Unannehmlichkeiten.\n\nViel Vergnügen mit den neuen und erweiterten Möglichkeiten des Auto Uploads. + Für %1$s %d ausgewählt %d ausgewählt @@ -666,7 +666,7 @@ Datenschutz Datei nicht gefunden! - Ordner auswählen + Ordner auswählen Prüfe Server-Verbindung diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 8a4db1809a12..fdf0f29b60ae 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -363,8 +363,8 @@ %1$s konnte nicht in den lokalen %2$s Ordner kopiert werden Sofort-Upload-Ordner - Lokales Verzeichnis - Entferntes Verzeichnis + Lokales Verzeichnis + Entferntes Verzeichnis Unterordner benutzen In Unterordnern speichern, basierend auf Jahr und Monat @@ -559,7 +559,7 @@ Suche Dies ist eine Nextcloud Funktion, bitte updaten. Mehr - Automatisches Hochladen + Automatisches Hochladen Mitmachen Hilf uns Testen Fehler gefunden? Komisches Verhalten? @@ -579,12 +579,12 @@ Verschieben nach… Kopieren nach… Wähle Verzeichnis… - Lade Verzeichnisse… - Keine Medienverzeichnisse gefunden. - Einstellungen Auto-Hochladen - Einstellungen - Sofortiger Upload wurde vollständig überarbeitet. Bitte über das Hauptmenü zum Auto Upload gehen und neu configurieren. Bitte entschuldige die Unannehmlichkeiten.\n\nViel Vergnügen mit den neuen und erweiterten Möglichkeiten des Auto Uploads. - Für %1$s + Lade Verzeichnisse… + Keine Medienverzeichnisse gefunden. + Einstellungen Auto-Hochladen + Einstellungen + Sofortiger Upload wurde vollständig überarbeitet. Bitte über das Hauptmenü zum Auto Upload gehen und neu configurieren. Bitte entschuldige die Unannehmlichkeiten.\n\nViel Vergnügen mit den neuen und erweiterten Möglichkeiten des Auto Uploads. + Für %1$s %d ausgewählt %d ausgewählt @@ -667,7 +667,7 @@ Datenschutz Datei nicht gefunden! - Ordner auswählen + Ordner auswählen Prüfe Server-Verbindung diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index b6eefb850111..c203edab8164 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -362,8 +362,8 @@ Το %1$s δεν μπόρεσε να αντιγραφεί στον τοπικό φάκελο %2$s Φάκελος άμεσης μεταφόρτωσης - Τοπικός φάκελος - Μετονομασία φακέλου + Τοπικός φάκελος + Μετονομασία φακέλου Χρήση υποφακέλων Αποθήκευση σε υποφακέλους με βάση το χρόνο και μήνα @@ -557,7 +557,7 @@ Αναζήτηση Αυτό είναι χαρακτηριστικό του Nextcloud, παρακαλούμε ενημερώστε. Μάθετε περισσότερα - Αυτόματη μεταφόρτωση + Αυτόματη μεταφόρτωση Συμμετοχή Βοηθήστε μας στις δοκιμές Βρήκατε σφάλμα; Κάτι σας φαίνεται παράξενο; @@ -577,12 +577,12 @@ Μετακίνηση σε… Αντιγραφή σε… Επιλογή φακέλου… - Φόρτωση φακέλων… - Δεν βρέθηκαν φάκελοι πολυμέσων. - Προτιμήσεις αυτόματης μεταφόρτωσης - Ρυθμίσεις - Η άμεση μεταφόρτωση έχει ανανεωθεί τελείως. Παρακαλούμε δείτε το κυρίως μενού και ρυθμίστε ξανά την αυτόματη μεταφόρτωση. Σας ζητούμε συγγνώμη για την ενόχληση\n\nΑπολαύστε τις νέες και εκτεταμένες δυνατότητες της αυτόματης μεταφόρτωσης! - Για %1$s + Φόρτωση φακέλων… + Δεν βρέθηκαν φάκελοι πολυμέσων. + Προτιμήσεις αυτόματης μεταφόρτωσης + Ρυθμίσεις + Η άμεση μεταφόρτωση έχει ανανεωθεί τελείως. Παρακαλούμε δείτε το κυρίως μενού και ρυθμίστε ξανά την αυτόματη μεταφόρτωση. Σας ζητούμε συγγνώμη για την ενόχληση\n\nΑπολαύστε τις νέες και εκτεταμένες δυνατότητες της αυτόματης μεταφόρτωσης! + Για %1$s %d επιλέχθηκε %d επιλέχθηκαν @@ -665,7 +665,7 @@ Ιδιωτικότητα Δεν βρέθηκε το αρχείο! - Ρύθμιση φακέλων + Ρύθμιση φακέλων Δοκιμή σύνδεσης με διακομιστή diff --git a/src/main/res/values-en-rGB/strings.xml b/src/main/res/values-en-rGB/strings.xml index 24c061746a89..136ded669c10 100644 --- a/src/main/res/values-en-rGB/strings.xml +++ b/src/main/res/values-en-rGB/strings.xml @@ -169,7 +169,7 @@ Upload failed, you need to log in again Uploads Current - Failed (tap to retry) + Failed / Pending restart Uploaded Completed Cancelled @@ -362,8 +362,8 @@ %1$s could not be copied to %2$s local folder Instant upload folder - Local folder - Remote folder + Local folder + Remote folder Use subfolders Store in subfolders based on year and month @@ -557,7 +557,7 @@ Search This is a Nextcloud feature, please update. Learn more - Auto upload + Auto upload Participate Help by testing Found a bug? Oddments? @@ -577,12 +577,12 @@ Move to… Copy to… Choose folder… - Loading folders… - No media folders found. - Preferences for auto uploading - Settings - Instant uploading has been revamped completely. Re-configure your auto upload from within the main menu.\n\nEnjoy the new and extended auto uploading. - For %1$s + Loading folders… + No media folders found. + Preferences for auto uploading + Settings + Instant uploading has been revamped completely. Re-configure your auto upload from within the main menu.\n\nEnjoy the new and extended auto uploading. + For %1$s %d selected %d selected @@ -665,7 +665,7 @@ Privacy File not found! - Configure folders + Configure folders Test server connection diff --git a/src/main/res/values-es-rAR/strings.xml b/src/main/res/values-es-rAR/strings.xml index e8fad3a34ce6..70b9f3370dc1 100644 --- a/src/main/res/values-es-rAR/strings.xml +++ b/src/main/res/values-es-rAR/strings.xml @@ -353,8 +353,8 @@ %1$s no pudo ser copiado a la carpeta local %2$s Carpeta de carga instantánea - Carpeta local - Carpeta remota + Carpeta local + Carpeta remota Usar sub carpetas Almacenar en sub carpetas con base en el año y mes @@ -535,7 +535,7 @@ Buscar Esta es una característica de Nextcloud, favor de actualizar. Conozca más - Carga automática + Carga automática Participe Ayúdenos probando ¿Encontró una falla? ¿Hay algo raro? @@ -547,12 +547,12 @@ Mover a… Copiar a… Seleccione la carpeta… - Cargando carpetas… - No se encontraron carpetas de medios - Preferencias de carga automática - Configuraciones - La carga instantánea ha sido completamente moderinzada. Reconfigure su carga automática desde el menu principal. \n\nDisfrute de las nuevas y extendidas capacidades de la carga automática. - Para %1$s + Cargando carpetas… + No se encontraron carpetas de medios + Preferencias de carga automática + Configuraciones + La carga instantánea ha sido completamente moderinzada. Reconfigure su carga automática desde el menu principal. \n\nDisfrute de las nuevas y extendidas capacidades de la carga automática. + Para %1$s %d seleccionado %d seleccionado @@ -632,6 +632,6 @@ Privacidad ¡Archivo no encontrado! - Configurar careptas + Configurar careptas diff --git a/src/main/res/values-es-rMX/strings.xml b/src/main/res/values-es-rMX/strings.xml index 103a3f795b6f..f184d9e9f4f9 100644 --- a/src/main/res/values-es-rMX/strings.xml +++ b/src/main/res/values-es-rMX/strings.xml @@ -362,8 +362,8 @@ %1$s no pudo ser copiado a la carpeta local %2$s Carpeta de carga automática - Carpeta local - Carpeta remota + Carpeta local + Carpeta remota Usar sub carpetas Almacenar en sub carpetas con base en el año y mes @@ -557,7 +557,7 @@ Buscar Esta es una característica de Nextcloud, por favor actualiza. Conoce más - Carga automática + Carga automática Participa Ayúdanos probando ¿Encontraste una falla? ¿Hay algo raro? @@ -577,12 +577,6 @@ Mover a… Copiar a… Seleccione la carpeta… - Cargando carpetas… - No se encontraron carpetas de medios - Preferencias de carga automática - Configuraciones - La carga automática ha sido completamente moderinzada. Reconfigura tu carga automática desde el menu principal. \n\nDisfruta de las nuevas y extendidas capacidades de la carga automática. - Para %1$s %d seleccionado %d seleccionado @@ -665,7 +659,5 @@ Privacidad ¡Archivo no encontrado! - Configurar carpetas - Probar la conexión del servidor diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index c26c2d8cb798..a4a4e7eb1383 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -362,8 +362,8 @@ %1$s no se ha podido copiar a la carpeta local %2$s Carpeta para subida instantánea - Carpeta local - Carpeta en servidor + Carpeta local + Carpeta en servidor Usar subcarpetas Almacenar en subcarpetas basadas en año y mes. @@ -557,7 +557,7 @@ Buscar Esta es una característica de Nextcloud. Por favor, actualice. Aprender más - Subida automática + Subida automática Participar Ayúdanos a realizar pruebas ¿Encontraste un error? ¿Algo va mal? @@ -577,12 +577,12 @@ Mover a… Copiar a… Elija carpeta… - Cargando carpetas… - No se han encontrado carpetas de medios. - Preferencias de subida automática - Configuración - La subida instantánea ha sido completamente renovada. Reconfigura tus subidas instantáneas en el menú principal.\n\n¡Disfruta las nuevas y extendidas capacidades de la subida automática! - Durante %1$s + Cargando carpetas… + No se han encontrado carpetas de medios. + Preferencias de subida automática + Configuración + La subida instantánea ha sido completamente renovada. Reconfigura tus subidas instantáneas en el menú principal.\n\n¡Disfruta las nuevas y extendidas capacidades de la subida automática! + Durante %1$s %d seleccionado %d seleccionados @@ -665,7 +665,7 @@ Privacidad ¡Archivo no encontrado! - Configurar carpetas + Configurar carpetas Probar conexión con servidor diff --git a/src/main/res/values-fi-rFI/strings.xml b/src/main/res/values-fi-rFI/strings.xml index c83b6ced574d..73d940410d35 100644 --- a/src/main/res/values-fi-rFI/strings.xml +++ b/src/main/res/values-fi-rFI/strings.xml @@ -290,8 +290,8 @@ Kuvan näyttäminen ei onnistunut Välitön lähetys kansio - Paikallinen kansio - Etäkansio + Paikallinen kansio + Etäkansio Käytä alikansioita Tallenna alikansioihin vuoden ja kuukauden mukaisesti. @@ -432,16 +432,16 @@ Etsi Tämä on Nextcloudin ominaisuus, päivitä se. Opi lisää - Automaattinen lähetys + Automaattinen lähetys Osallitu Julkaisu ehdokas Siirrä kohteeseen… Kopio kohteeseen… Valitse kansio… - Ladataan kansiot… - Media kansioita ei löytynyt. - Asetukset - %1$s varten + Ladataan kansiot… + Media kansioita ei löytynyt. + Asetukset + %1$s varten %d valittu %d valittu diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 27d07cc1f241..7979b2a69408 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -362,8 +362,8 @@ %1$s n\'a pas pu être copié dans le dossier local %2$s Téléversement immédiat du dossier - Dossier local - Dossier distant + Dossier local + Dossier distant Utiliser des sous-dossiers Stocker dans des sous-dossiers basés sur années et mois @@ -557,7 +557,7 @@ Rechercher Il s\'agit d\'une fonctionnalité de Nextcloud ; veuillez mettre à jour.. En apprendre plus - Téléversement automatique + Téléversement automatique Participer Aidez nous en le testant Vous avez trouvé un bug ? Quelque chose vous semble étrange ? @@ -577,12 +577,12 @@ Déplacer vers… Copier vers… Sélectionner le dossier… - Chargement des dossiers… - Aucun dossier média trouvé. - Préférences pour le téléversement automatique - Paramètres - Le téléversement immédiat a été complètement réorganisé. Merci de bien vouloir accéder au menu principal et de reconfigurer votre téléversement automatique.\n\nProfitez des nouvelles capacités étendues du téléversement automatique. - Pour %1$s + Chargement des dossiers… + Aucun dossier média trouvé. + Préférences pour le téléversement automatique + Paramètres + Le téléversement immédiat a été complètement réorganisé. Merci de bien vouloir accéder au menu principal et de reconfigurer votre téléversement automatique.\n\nProfitez des nouvelles capacités étendues du téléversement automatique. + Pour %1$s %d sélectionné %d sélectionnés @@ -665,7 +665,7 @@ Vie privée Fichier non trouvé ! - Configurer les dossiers + Configurer les dossiers Tester la connexion du serveur diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml new file mode 100644 index 000000000000..df16e34418a1 --- /dev/null +++ b/src/main/res/values-gl/strings.xml @@ -0,0 +1,664 @@ + + + Aplicación %1$s Android + versión %1$s + Actualizar a conta + Enviar + Contido doutras aplicacións + Ficheiros + Abrir con + Novo cartafol + Axustes + Detalles + Enviar + Ordenar + Ordenar por + Ordenar por + A - Z + Z - A + Primeiro o máis recente + Primeiro o máis antigo + Primeiro o máis grande + Primeiro o máis pequeno + + Todos os ficheiros + Ficheiros + Inicio + Favoritos + Fotos + No dispositivo + Engadido recentemente + Modificado recentemente + Compartido + Vídeos + Axustes + Envíos + Actividades + Notificacións + Usado %1$s de %2$s + Pechar + Abrir + Xeral + Máis + Contas + Xestionar as contas + Código de bloqueo + Bloqueo coa pegada dactilar + Non foi estabelecida ningunha pegada dactilar + Amosar os ficheiros agochados + Envío instantáneo de imaxes + Enviar instantaneamente as imaxes da cámara + Envío instantáneo de vídeos + Enviar instantaneamente os vídeos gravados coa cámara + Activar o rexistro + Isto empregase para rexistrar os problemas + Historial do rexistro + Isto amosa os rexistros gravados + Eliminar o historial + Sincronizar calendario e contactos + Configurar DAVdroid (v1.3.0 +) para a conta actual + DAVdroid non foi quen de resolver o enderezo do servidor para a conta + Nin F-Droid nin Google Play están instalados + Configurada a sincronización do calendario e dos contactos + Axuda + Recomendar a un amigo + Comentarios + Exención de responsabilidade + Lembrar a localización do elemento compartido + Lembrar a localización do elemento compartido usado máis recentemente + + Probe %1$s no seu teléfono intelixente! + Quixera convidalo a usar %1$s no seu teléfono intelixente\nDescargueo aquí: %2$s + + Comprobar o servidor + Enderezo do servidor https://… + Nome de usuario + Contrasinal + Aínda non ten un servidor? \nPrema aquí para obter un dun provedor + Ficheiros + Conectar + Enviar + Seleccione o cartafol de envío + Non se atoparon contas + Non existen contas %1$s no seu dispositivo. Configure primeiro unha conta. + Instalación + Saír + Non hai ficheiros para enviar + %1$s non pode enviar un fragmento de texto coma se for un ficheiro. + Os datos recibidos non inclúen un ficheiro correcto. + Non é posíbel enviar este ficheiro + %1$s non ten permisos para ler o ficheiro recibido + Non foi atopado o ficheiro seleccionado para enviar. Comprobe se existe o ficheiro + Non foi posíbel copiar o ficheiro nun cartafol temporal. Tente volver envialo. + Opción de envío: + Mover o ficheiro cara o cartafol de Nextcloud + Manter o ficheiro no cartafol de orixe + Eliminar o ficheiro do cartafol de orixe + segundos atrás + Aquí non hai ficheiros + Envíe algún contido ou sincronice cos seus dispositivos + Marque como favoritos algúns ficheiros ou sincronice cos seus dispositivos! + Os ficheiros e cartafoles que marque como favoritos amosaranse aquí + A súa busca non atopou ningún ficheiro favorito. + Cargando… + Non foi estabelecida unha aplicación para manexar este tipo de ficheiro. + Non hai ficheiros neste cartafol. + Non hai resultados neste cartafol + Sen resultados + Aínda non hay nada marcado como favorito + Aínda non hay nada compartido + Os ficheiros e cartafoles que comparta amosaranse aquí + Non hai vídeos + Non hai fotos + Quizáis estea nun cartafol diferente? + Non se atoparon ficheiros modificados nos últimos 7 días. + A súa busca non atopou ficheiros que teñan sido modificados +nos últimos 7 días. + Non se atoparon ficheiros engadidos recentemente + A súa busca non atopou ningún ficheiro engadido recentemente. + Envíe algunhas fotos ou active o envío automático. + A súa busca non atopou ningunha fotografía. + Envíe algún vídeo ou active o envío automático. + A súa busca non atopou ningún vídeo. + Non hai envío dispoñíbeis + Envíe algún contido ou active o envío automático. + Envíe algún contido ou active o envío automático. + cartafol + cartafoles + ficheiro + ficheiros + Toque nun ficheiro para que amose a información adicional. + Tamaño: + Tipo: + Creado: + Modificado: + Descargar + Sincronizar + O ficheiro foi renomeado a %1$s durante o envío + Ver como lista + Compartir + Si + Non + Aceptar + Retirar o envío + Tentar de novo o envío + Cancelar a sincronización + Cancelar + Atrás + Gardar + Gardar e saír + Erro + Cargando … + descoñecido + Produciuse un erro descoñecido + Pendente + Sobre + Cambiar o contrasinal + Retirar a conta + Eliminar a conta %s?\n\n Esta acción non pode desfacerse. + Crear unha conta + Enviar desde … + Nome do cartafol + Enviando … + %1$d%% enviando %2$s + Enviado + Enviado %1$s + Produciuse unha falla no envío + Non foi posíbel enviar: %1$s + O envío fracasou, é necesario que se autentique de novo. + Envíos + Actual + Produciuse unha falla (toque para volver tentalo) + Enviado + Completado + Cancelado + Pausado + Produciuse un erro de conexión + En breve volverá a iniciarse o envío + Produciuse un erro de credenciais + Produciuse un erro de cartafol + Produciuse un erro de ficheiro + Non se atopou o ficheiro local + Erro nos permisos + Conflito + A aplicación interrompeuse + Produciuse un erro descoñecido + Agardando pola conexión WiFi + Agardando polo envío + Descargando … + %1$d%% descargando %2$s + Descargado + Descargando %1$s + Produciuse unha falla na descarga + Non foi posíbel descargar %1$s + Non descargado aínda + A descarga fracasou, é necesario que se autentique de novo. + Escoller unha conta + Produciuse unha falla na sincronización + Produciuse unha falla na sincronización, é necesario que se autentique de novo. + Non foi posíbel completar a sincronización de %1$s + Contrasinal incorrecto para %1$s + Atopáronse conflictos + %1$d ficheiros «manter sincronizados» non foi posíbel sincronizalos + Produciuse unha falla no mantemento sincronizado de ficheiros + Non foi posíbel sincronizar o contido de %1$d ficheiros (%2$d conflitos) + Algúns ficheiros locais foron esquecidos + Non é posíbel copiar %1$d ficheiros do cartafol %2$s en + A partires da versión 1.3.16, os ficheiros enviados desde este dispositivo cópianse no cartafol local %1$s para evitar a perda de datos cando se sincroniza un único ficheiro con varias contas.\n\nPor mor deste cambio, todos os ficheiros enviados con versións anteriores desta aplicación foron copiados no cartafol %2$s. Porén, un erro impediu que se completara esta operación durante a sincronización da conta. Pode deixar os ficheiros tal e como están e eliminar a ligazón cara %3$s ou mover los ficheiros para o cartafol %1$s e manter a ligazón cara %4$s.\n\nEmbaixo amósanse os ficheiros locais e os ficheiros remotos en %5$s aos que foron enlazados. + O cartafol %1$s xa non existe + Mover todo + Foron movidos todos os ficheiros + Algúns ficheiros non puideron seren movidos + Local: %1$s + Remoto: %1$s + Non hai espazo abondo para copiar os ficheiros seleccionados no cartafol %1$s. No canto diso, gustaríalle movelos? + Introduza o seu código de seguridade + + Introduza o seu código de seguridade + Solicitaráselle o código de seguridade cada vez que inicie a aplicación + Introduza de novo o seu código de seguridade + Retirar o seu código de seguridade + Os códigos de seguridade non son iguais + Código de seguridade incorrecto + Retirouse o código de seguridade + O código de seguridade foi almacenado + + %1$s reprodutor musical + %1$s (reproducindo) + %1$s (cargando) + Rematou a reprodución de %1$s + Non se atopan ficheiros multimedia + Non foi fornecida unha conta + O ficheiro non está nunha conta correcta + Códec multimedia non admitido + Non foi posíbel ler o ficheiro multimedia + O ficheiro multimedia ten unha codificación incorrecta + O intento de reproducir o ficheiro esgotou o tempo de espera. + O ficheiro multimedia non pode ser transformado nun fluxo + O reprodutor predeterminado non pode reproducir o ficheiro mltimedia + Produciuse un erro de seguridade ao tentar reproducir %1$s + Produciuse un erro de entrada ao tentar reproducir %1$s + Produciuse un erro non agardado ao tentar reproducir %1$s + Botón de retroceso + Botón de reprodución/pausa + Botón de avance rápido + + Obtendo autorización … + Tentando acceder a … + Sen conexión de rede + Non hai conexións seguras dispoñíbeis. + Estabeleceuse a conexión + Probando a conexión + Configuración errada do servidor + Xa existe unha conta en este dispositivo cos mesmos datos de usuario e servidor + O usuario que inseriu non coincide co usuario desta conta + Produciuse un erro descoñecido! + Non foi posíbel atopar a máquina + Non se atopou o servidor + O servidor tardou de máis en responder + O formato do enderezo do servidor é incorrecto + Produciuse unha falla ao preparar o SSL + Non foi posíbel verificar a identidade do servidor SSL + Versión do servidor non recoñecida + Non foi posíbel establecer a conexión + Estabeleceuse unha conexión segura + Nome de usuario ou contrasinal incorrecto + A autorización non foi aceptada + O acceso foi denegado polo servidor de autorización + Estado non agardado; introduza de novo o enderezo do servidor + A súa autorización caducou. Autorícese de novo + Introduza o seu contrasinal actual + A súa sesión caducou. Acceda de novo + Conectando co servidor de autenticación… + O servidor non admite este método de autenticación + %1$s non admite contas múltipes + O seu servidor non está devolvendo unha identificación de usuario correcta; contacte cun administrador + Non é posíbel autenticarse neste servidor + A conta aínda non existe no dispositivo + + + Estabelecer como dispoñíbel sen conexión + Estabelecer como dispoñíbel con conexión + Estabelecer como favorito + Retirar de favoritos + Renomear + Retirar + Confirma que quere retirar %1$s? + Confirma que quere retirar %1$s e todo o seu contido? + Só local + Retirado + Non foi posíbel retiralo + Introduza un nome novo + Non foi posíbel renomear a copia local, ténteo cun un nome diferente + Non foi posíbel renomear o servidor + Non foi posíbel comprobar o ficheiro remoto + Os contidos do ficheiro xa están sincronizados + Non foi posíbel crear o cartafol + Caracteres non permitidos: / \\ < > : \" | ? * + O nome de ficheiro contén algún carácter incorrecto + O nome de ficheiro non pode estar baleiro + Agarde un chisco + Comprobando as credenciais almacenadas + Produciuse un problema non agardado, seleccione o ficheiro desde una aplicación diferente + Non seleccionou ningún ficheiro + Enviar a ligazón a … + Copiando o ficheiro desde o almacenamento privado + + Acceder con oAuth2 + Conectando co servidor oAuth2… + + Non foi posíbel verificar a identidade do sitio + - O certificado do servidor non é de confianza + - O certificado do servidor caducou + - As datas de validez do certificado están son do futuro + - O URL non coincide co nome de máquina no certificado + Aínda así, quere fiar neste certificado igualmente? + Non foi posíbel gardar o certificado + Detalles + Agochar + Emitido para: + Emitido por: + Nome común: + Organización: + Departamento da organización: + País + Estado: + Lugar: + Validez: + De: + A: + Sinatura: + Algoritmo: + Este algoritmo de resumo non está dispoñíbel no seu teléfono. + Pegada dactilar + Existe un problema ao cargar o certificado. + Non é posíbel amosar o certificado. + - Non hai información sobre este erro + + Isto é un marcador de posición + placeholder.txt + Imaxe PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 + + Enviar só con WiFi + Enviar fotografías só con WiFi + Enviar vídeos só con WiFi + Enviar só cando estea cargando + Enviar só cando estea cargando + /EnvíoInstantáneo + /Envío automático + Conflito de ficheiro + Que ficheiros quere manter? Se selecciona ámbalas dúas versións, engadiráselle un número ao nome do ficheiro local. + Manter ambos + versión local + versión no servidor + + Disculpe. + Vista previa da imaxe + Non é posíbel amosar a imaxe + + Non foi posíbel copiar %1$s no cartafol local %2$s + Cartafol de envío instántaneo + Cartafol local + Cartafol remoto + Usar subcartafoles + Arquivar en subcartafoles baseados en ano e mes. + + O seu servidor non ten activada a compartición. Póñase en contacto co administrador + Non se puido actualizar. Comprobe que o ficheiro existe + Produciuse un erro ao tentar compartir este ficheiro ou cartafol + Non foi posíbel deixar de compartir. Comprobe que existe o ficheiro + Produciuse un erro ao tentar deixar de compartir este ficheiro ou cartafol + Non se puido actualizar. Comprobe que o ficheiro existe + Produciuse un erro ao tentar actualizar o elemento compartido + Escriba un contrasinal + Ten que escribir un contrasinal + + Enviar + + Copiar a ligazón + Copiado no portapapeis. + Non se ha recibiu ningún texto para copiar no portapapeis + Produciuse un erro non agardado ao copiar no portapapeis + Texto copiado desde %1$s + + produciuse un erro crítico: Non é posíbel realizar operacións + + produciuse un erro durante a conexión co servidor + Produciuse un erro agardando a resposta do servidor. Non foi posíbel completar a operación. + Produciuse un erro agardando a resposta do servidor. Non foi posíbel completar a operación. + Non foi posíbel completar a operación. O servidor non está dispoñíbel + + Non ten permiso %s + para renomear este ficheiro + para eliminar este ficheiro + para compartir este ficheiro + para deixar de compartir este ficheiro + para actualizar esta compartición + para crear este ficheiro + para enviar este cartafol + O ficheiro xa non está dispoñíbel no servidor + + Actualizando a ruta do almacenamento + Rematar + Preparando a migración… + Comprobando o destino… + Gardando a configuración das contas… + Agardando que rematen todas as sincronizacións… + Movendo datos… + Actualizando o índice… + Limpando… + Restaurando a configuración das contas… + Rematado + ERRO: Non hai espazo abondo + ERRO: Non se pode escribir no ficheiro de destino + ERRO: Non se pode ler o ficheiro de orixe + ERRO: xa existe o directorio de Nextcloud + ERRO: Produciuse unha falla durante a migración + ERRO: Produciuse unha falla ao actualizar o índice + + Xa existe o cartafol de datos. Escolla un dos seguintes: + Substituír + Usar + + Non se pode ler o ficheiro de orixe + Aínda quere cambiar a ruta do almacenamento a |%1$s?\n\nNota: haberá que volver descargar todos os datos. + + Contas + Engadir unha conta + Administrar contas + A conexión segura está a ser redirixida a través dunha ruta insegura. + + Rexistros + Enviar o historial + Non se atopou ningunha aplicación para o envío de rexistros. Instale un cliente de correo-e. + Rexistros da aplicación %1$s Android + Cargando datos … + + Requírese autenticación + Contrasinal incorrecto + Mover + Copiar + Aquí non hai nada. Pode engadir un cartafol. + Escoller + + Non se puido mover o ficheiro. Comprobe que o ficheiro existe + Non é posíbel mover un cartafol cara un dos seus propios cartafoles + Este ficheiro xa existe no cartafol de destino + Produciuse un erro ao tentar mover este ficheiro ou cartafol. + para mover este ficheiro + + + Non se puido copiar. Comprobe que o ficheiro existe + Non é posíbel copiar un cartafol nun dos seus propios cartafoles + Este ficheiro xa existe no cartafol de destino + Oconteceu un erro mentras se intentaba copiar este ficheiro ou cartafol + copiar este ficheiro + + Envíos instantáneos + Detalles + + Cartafol de vídeo para envíos instantáneos + A sincronización do cartafol %1$s non se completou + + compartido + con vostede + + %1$s compartiu «%2$s» con vostede + «%1$s» foi compartido con vostede + + Actualizar a conexión + Enderezo do servidor + Non hai memoria abondo + + Nome de usuario + + 1 cartafol + %1$d cartafoles + 1 ficheiro + 1 ficheiro, 1 cartafol + 1 ficheiro, %1$d cartafoles + %1$d ficheiros + %1$d ficheiros, 1 cartafol + %1$d ficheiros, %2$d cartafoles + Establecer a imaxe como + Estabelecer como + + O ficheiro orixinal vai ser… + O ficheiro orixinal vai ser… + Copiar o ficheiro + Mover o ficheiro + Seleccionar todo + + mantense no cartafol orixinal + movido car o cartafol da aplicación + eliminado + Ruta de almacenamento + Común + + Compartindo + Compartir con %1$s + Compartir con outros usuarios e grupos + Aínda non hai datos compartidos con usuarios + Engadir usuario ou grupo + Compartir ligazón + Definir a data de caducidade + Protexer con contrasinal + Asegurado + Permitir a edición + Agochar a lista de ficheiros + Obter a ligazón + Compartir con … + Compartir con %1$s + + Buscar + + Buscar usuarios e grupos + %1$s (grupo) + %1$s (remoto) + %1$s (correo-e) + %1$s ( ás %2$s ) + + Actualice a versión do servidor para permitir compartir entre usuarios dende os seus clientes.\nContacte co administrador + pode compartir + pode editar + crear + cambio + eliminar + Deixar de compartir + feito + + Fracasou o novo intento + Fracasou a limpeza + Limpo + Limpar os envíos rematados + + Vista como grella + Vista como lista + + Administrar o espazo + Van ser eliminados permanentemente as configuracións , base de datos e certificados do servidor de datos de %1$s.\n\nOs ficheiros descargados manteranse sen cambios.\n\nEste proceso pode levar bastante tempo. + Limpar os datos + Algúns ficheiros non puideron ser eliminados + + Requirense permisos adicionais para enviar e descargar ficheiros. + Non se atopou o ficheiro no sistema local de ficheiros + Confirma que quere retirar os elementos seleccionados? + Confirma que quere retirar os elementos seleccionados e o seu contido? + Servidor en modo de mantemento + + Agardando pola carga + Buscar + Esta é unha funcionalidade de Nextcloud, actualice. + Aprender máis + Envío automático + Participar + Axudanos facendo probas + Atopaches un fallo? hai algo estraño? + Informe dun fallo no GitHub + Iinteresaríalle axudarnos a probar a seguinte versión? + Probar a versión de desenvolvemento + Isto inclúe todas las últimas funcionalidades y es lo más nuevo. Poden ocorrer fallas e erros e, se é o caso, infórmenos diso. + Candidata de publicación + A candidata de publicación (release candidate - RC) é unha foto da versión máis proxima a publicar e agardase que sexa estábel. Probar a súa configuración individual podería axudarnos a asegurar iso. Rexístrese para facer probas na Play Store ou consulte directamente a sección de «versións» en F-Droid. + Colabore activamente + Únase ás conversas no IRC: <a href=\\"%1$s\\">#nextcloud-mobile</a> + Axude a outros no <a href=\\"%1$s\\">foro</a> + <a href=\\"%1$s\\">Traduza</a> a aplicación + Revise, corrixa e escriba código, vexa <a href=\\"%1$s\\">CONTRIBUIR.md</a> para obter máis detalles + Mover para… + Copiar en… + Escolla o cartafol… + Cargando cartafoles… + Non se atopan cartafoles multimedia + Preferencias de envío automático + Axustes + O envío automático foi renovado completamente. Reconfigure os seus envíos instantáneos no menú principal.\n\nGoce das novas e ampliadas capacidades do envío automático! + Para %1$s + + %d selecionado + %d selecionados + + + Cargando actividades… + Non se atoparon actividades. + + Cargando notificacións… + Non hai notificacións + Compróbeo máis tarde. + + Introduza o nome e o tipo do ficheiro a enviar + Nome de ficheiro + Tipo de ficheiro + Fragmento de teto(.txt) + Ficheiro de atallo a internet(%s) + Ficheiro de atallo aGoogle Maps(%s) + + Predeterminado + Tarxeta SD %1$d + Descoñecido + + + Que hai de novo no Nextcloud + + + Un lugar seguro para todos os seus datos + Acceda, comparta e protexa os seus ficheiros na casa e na oficina + + Múltiples contas + Conéctese a todas as aúas nubes + + Envío instantáneo + Manteña as súas fotos a seguro + + Omitir + + Escanée o seu dedo + O dedo non foi recoñecido + + + Nome completo + Correo-e + Número de teléfono + Enderezo + Sitio web + Twitter + + Información do usuario + + + Aínda non houbo actividade + Este fluxo amosaralle eventos como\nadicións, cambios e elementos compartidos + Produciuse un erro + Sobre + + Copia de seguridade dos contactos + Restaurar os contactos + Facer agora a copia de seguridade + Copia de seguridade dos contactos + Última copia de seguridade + Necesitase permiso para ler a lista de contactos + Necesitase permiso para cambiar a lista de contactos + Restaurar os contactos + Restaurar os contactos seleccionados + Escolla unha conta para importala + Non se concederon permisos. Non se importou nada! + Escolla a data + nunca + Non se atopou o ficheiro + Non foi posíbel atopar a súa última copia de seguridade! + A copia de seguridade está programada e comezará en breve + A importación está programada e comezará en breve + + + Recibiuse unha nova notificación + Saír + Non se atopou unha aplicación coa que estabelecer unha imaxe! + Intimidade + Non se atopou o ficheiro! + + Configurar os cartafoles + + diff --git a/src/main/res/values-he/strings.xml b/src/main/res/values-he/strings.xml index aa25048961ca..fbd1e227e647 100644 --- a/src/main/res/values-he/strings.xml +++ b/src/main/res/values-he/strings.xml @@ -223,8 +223,8 @@ תצוגה מקדימה לתמונה %1$s לא ניתן להעתקה לתיקייה מקומית %2$s - תיקייה מקומית - תיקייה מרוחקת + תיקייה מקומית + תיקייה מרוחקת לא ניתן לשתף. יש לבדוק אם הקובץ קיים שגיאה אירעה בזמן ניסיון לשתף קובץ זה או תיקייה זו לא ניתן לבטל שיתוף. יש לבדוק אם הקובץ קיים @@ -350,8 +350,8 @@ העתק אל… בחר תיקייה… - לא נמצאו תיקיות מדיה - הגדרות + לא נמצאו תיקיות מדיה + הגדרות שם קובץ סוג קובץ ברירת מחדל diff --git a/src/main/res/values-hu-rHU/strings.xml b/src/main/res/values-hu-rHU/strings.xml index 35c607551093..9b09ad338df2 100644 --- a/src/main/res/values-hu-rHU/strings.xml +++ b/src/main/res/values-hu-rHU/strings.xml @@ -293,8 +293,8 @@ Előnézeti kép %1$s nem lehet másolni a %2$s helyi mappába Azonnali feltöltés mappa - Helyi mappa - Távoli mappa + Helyi mappa + Távoli mappa Almappák használata Tárolás év és hónap szerinti almappákban @@ -429,17 +429,19 @@ Keresés Ez egy Nextcloud funkció, kérlek frissíts. Tudj meg többet - Automatikus feltöltés + Automatikus feltöltés Részvétel Kiadásra jelölt Aktívan hozzájárul Áthelyezés ide...… Másolás ide...… Mappa választás… - Mappák betöltése… - Nem találhatók média mappák. - Beállítások - Neki: %1$s + Mappák betöltése… + Nem találhatók média mappák. + Automatikus feltöltés beállításai + Beállítások + Az azonnali feltöltés teljesen át lett alakítva. Kérlek a főmenüben állítsd be újra az automatikus feltöltést. Elnézést a kellemetlenségért.\n\nÉlvezd az új és kibővített automatikus feltöltési képességet! + Neki: %1$s %d kiválasztott %d kiválasztott diff --git a/src/main/res/values-id/strings.xml b/src/main/res/values-id/strings.xml index 0eaba31f98bf..ef52265327fa 100644 --- a/src/main/res/values-id/strings.xml +++ b/src/main/res/values-id/strings.xml @@ -238,8 +238,8 @@ Pratilik gambar %1$s tidak dapat disalin ke folder lokal %2$s Folder unggah cepat - Folder lokal - Folder remote + Folder lokal + Folder remote Gunakan subfolder Simpan dalam subfolder berdasarkan tahun dan bulan @@ -374,17 +374,19 @@ Cari Ini adalah fitur Nextcloud, harap perbarui. Pelajari lebih lanjut - Unggah otomatis + Unggah otomatis Berpartisipasi Kandidat rilis Berkontribusi Aktif Pindah ke… Salin ke… Pilih folder… - Memuat folder… - Tidak ada folder media yang ditemukan. - Pengaturan - Untuk %1$s + Memuat folder… + Tidak ada folder media yang ditemukan. + Preferensi unggah otomatis + Pengaturan + Unggah instan telah dirubah sepenuhnya. Harap lihat menu utama dan konfigurasi ulang unggah otomatis anda. Maaf untuk gangguannya.\n\nNikmati kemampuan unggah otomatis yang lebih jauh dan baru! + Untuk %1$s %d dipilih diff --git a/src/main/res/values-is/strings.xml b/src/main/res/values-is/strings.xml index be1d8e1e8f8e..bba2cb902292 100644 --- a/src/main/res/values-is/strings.xml +++ b/src/main/res/values-is/strings.xml @@ -363,8 +363,8 @@ Smelltu hér til að fá þér einn frá þjónustuaðila. %1$s var ekki hægt að afrita í staðværu %2$s möppuna Mappa fyrir beinar innsendingar - Staðvær mappa - Fjartengd mappa + Staðvær mappa + Fjartengd mappa Nota undirmöppur Geyma í undirmöppum byggðum á ári og mánuðum @@ -558,7 +558,7 @@ Smelltu hér til að fá þér einn frá þjónustuaðila. Leita Þetta er Nextcloud-eiginleiki, endilega uppfærðu. Læra meira - Sjálfvirk innsending + Sjálfvirk innsending Taka þátt Hjálpaðu til við prófanir Fannstu villu? Skringilegheit? @@ -578,12 +578,12 @@ Smelltu hér til að fá þér einn frá þjónustuaðila. Færa í… Afrita í… Veldu möppu… - Hleð inn möppum… - Engar margmiðlunarmöppur fundust - Kjörstillingar fyrir sjálfvirkar innsendingar - Stillingar - Beinar innsendingar hafa verið algerlega endurhannaðar. Endurstilltu sjálfvirkar innsendingar beint í aðalvalmyndinni\n\nNjóttu góðs af nýju og ítarlegu viðmóti sjálfvirkra innsendinga. - Fyrir %1$s + Hleð inn möppum… + Engar margmiðlunarmöppur fundust + Kjörstillingar fyrir sjálfvirkar innsendingar + Stillingar + Beinar innsendingar hafa verið algerlega endurhannaðar. Endurstilltu sjálfvirkar innsendingar beint í aðalvalmyndinni\n\nNjóttu góðs af nýju og ítarlegu viðmóti sjálfvirkra innsendinga. + Fyrir %1$s %d valið %d valið @@ -666,7 +666,7 @@ Smelltu hér til að fá þér einn frá þjónustuaðila. Gagnaleynd Skrá finnst ekki! - Stilla möppur + Stilla möppur Prófa tengingu þjóns diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 27166d5d0c59..40ea26fdefdc 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -296,8 +296,8 @@ %1$s non può essere copiato nella cartella locale %2$s Cartella caricamenti istantanei - Cartella locale - Cartella remota + Cartella locale + Cartella remota Usa sottocartelle Archivia in sottocartelle in base a anno e mese @@ -454,7 +454,7 @@ Cerca Questa è una funzionalità di Nextcloud, aggiorna. Scopri altro - Carica automaticamente + Carica automaticamente Partecipa Prova la versione di sviluppo Candidata al rilascio @@ -462,10 +462,10 @@ Sposta in… Copia in… Scegli la cartella… - Caricamento cartelle… - Nessuna cartella di file multimediali trovata. - Impostazioni - Per %1$s + Caricamento cartelle… + Nessuna cartella di file multimediali trovata. + Impostazioni + Per %1$s %d selezionato %d selezionati @@ -536,6 +536,6 @@ Riservatezza File non trovato! - Configura cartelle + Configura cartelle diff --git a/src/main/res/values-ja-rJP/strings.xml b/src/main/res/values-ja-rJP/strings.xml index 130628c2279e..07b0e5093f15 100644 --- a/src/main/res/values-ja-rJP/strings.xml +++ b/src/main/res/values-ja-rJP/strings.xml @@ -290,8 +290,8 @@ イメージプレビュー %1$s は、ローカルフォルダー %2$s にコピーできませんでした。 自動アップロードフォルダー - ローカルフォルダー - リモートフォルダー + ローカルフォルダー + リモートフォルダー サブフォルダーを利用 年と月を元にしたサブフォルダーに保存 @@ -426,7 +426,7 @@ 検索 これはNextCloudの機能です。更新してください。 もっと見る - 自動アップロード + 自動アップロード 参加する 開発バージョンをテスト リリース候補 @@ -434,10 +434,10 @@ …に移動 …にコピー … フォルダーを選択 - … フォルダを読み込み中 - メディアフォルダが見つかりませんでした - 設定 - %1$s の + … フォルダを読み込み中 + メディアフォルダが見つかりませんでした + 設定 + %1$s の %d選択されています。 diff --git a/src/main/res/values-lv/strings.xml b/src/main/res/values-lv/strings.xml index 2acbb9550c17..8ca536a79f2d 100644 --- a/src/main/res/values-lv/strings.xml +++ b/src/main/res/values-lv/strings.xml @@ -252,8 +252,8 @@ Attēla priekšskatījums %1$s nevar kopēt uz %2$s lokālo mapi Tūlītējas augšupielādes mepe - Lokāla mape - Attālinātā mape + Lokāla mape + Attālinātā mape Lietot apakšmapi Nevar koplietot. Lūdzu, pārbaudiet, vai fails pastāv Radās kļūda, mēģinot koplietot šo failu vai mapi @@ -377,14 +377,19 @@ Vai tiešām vēlies noņemt izvēlētos un to saturu? Meklēt Uzziniet vairāk - Automātiska augšupielāde + Automātiska augšupielāde + Palīdziet mums testēšanā + Ziņojiet par problēmu Github + Pievienojieties tērzēšanas ar IRC: <a href=\"%1$s\">#nextcloud-mobile</a> + Palīdziet citiem <a href=\"%1$s\">forumā</a> + <a href="%1$s">Tūlkot</a> programmu Pārvietot uz… Kopēt uz… Izvēlies mapi… - Ielāde mapes… - Nav atrasta multivides mape. - Iestatījumi - %1$s + Ielāde mapes… + Nav atrasta multivides mape. + Iestatījumi + %1$s %d atlasīts %d atlasīts diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml index 06b11f0ef2a7..025abc842332 100644 --- a/src/main/res/values-nb-rNO/strings.xml +++ b/src/main/res/values-nb-rNO/strings.xml @@ -362,8 +362,8 @@ %1$s kunne ikke kopieres til lokal mappe %2$s Mappe for umiddelbar opplasting - Lokal mappe - Ekstern mappe + Lokal mappe + Ekstern mappe Bruk undermapper Lagre filer i undermapper basert på år og måned @@ -556,7 +556,7 @@ Søk Dette er en Nextcloud funksjon, oppdater. Lær mer - Auto-opplasting + Auto-opplasting Delta Hjelp oss å teste Funnet en feil? Føles noe rart? @@ -575,12 +575,12 @@ Flytt til… Kopier til… Velg mappe… - Laster inn mapper… - Ingen mediamapper funnet. - Innstillinger for auto-opplasting - Innstillinger - Umiddelbar opplasting er fullstendig omarbeidet. Se hovedmenyen og konfigurer auto-opplasting på nytt. Beklager uleiligheten.\n\nTa i bruk de nye og utvidede mulighetene i auto-opplasting. - For %1$s + Laster inn mapper… + Ingen mediamapper funnet. + Innstillinger for auto-opplasting + Innstillinger + Umiddelbar opplasting er fullstendig omarbeidet. Se hovedmenyen og konfigurer auto-opplasting på nytt. Beklager uleiligheten.\n\nTa i bruk de nye og utvidede mulighetene i auto-opplasting. + For %1$s %d valgt %d valgte @@ -663,7 +663,7 @@ Personvern Finner ikke filen! - Sett opp mapper + Sett opp mapper Test tjenertilkobling diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 302e2a6df0f8..9e7225851c40 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -362,8 +362,8 @@ Kies er eentje van een provider. %1$s kon niet worden gekopieerd naar de lokale map %2$s Directe-uploadmap - Lokale map - Externe map + Lokale map + Externe map Gebruik submappen Opslaan in submappen, gebaseerd op jaar en maand @@ -554,7 +554,7 @@ Kies er eentje van een provider. Zoeken Dit is een Nextcloud-optie, gelieve te updaten. Meer weten - Automatisch uploaden + Automatisch uploaden Meedoen Help bij testen Bug gevonden? Rare dingen? @@ -571,12 +571,12 @@ Kies er eentje van een provider. Verplaats naar … Kopieer naar … Kies map … - Mappen laden … - Geen mediamappen gevonden. - Voorkeuren voor automatisch uploaden - Instellingen - Direct uploaden is helemaal herzien. Bekijk het hoofdmenu en herconfigureer je auto-uploadfunctie. Onze excuses voor het ongemak.\n\nVeel plezier met de nieuwe en meer uitgebreide auto-upload-mogelijkheden! - Voor %1$s + Mappen laden … + Geen mediamappen gevonden. + Voorkeuren voor automatisch uploaden + Instellingen + Direct uploaden is helemaal herzien. Bekijk het hoofdmenu en herconfigureer je auto-uploadfunctie. Onze excuses voor het ongemak.\n\nVeel plezier met de nieuwe en meer uitgebreide auto-upload-mogelijkheden! + Voor %1$s %d geselecteerd %d geselecteerd @@ -659,7 +659,7 @@ Kies er eentje van een provider. Privacy Bestand niet gevonden! - Mappen instellen + Mappen instellen Test server verbinding diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 5fdae4f81ce4..96827bb18b8d 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -357,8 +357,8 @@ %1$s nie może zostać skopiowany do lokalnego folderu %2$s Folder natychmiastowego wysyłania - Folder lokalny - Folder zdalny + Folder lokalny + Folder zdalny Używaj podfolderów Zapisz w podfolderach opartych na roku oraz miesiącu @@ -539,7 +539,7 @@ Wyszukaj Ta funkcja jest dostępna w Nextcloud, prosimy wykonać aktualizację Dowiedz się więcej - Automatyczne przesyłanie + Automatyczne przesyłanie Wspieraj Pomóż nam testować Znaleziono błąd? Jest coś dziwnego? @@ -551,12 +551,12 @@ Przenieś do… Skopiuj do… Wybierz folder… - Ładowanie folderów… - Nie znaleziono folderów multimedialnych. - Preferencje automatycznego wysyłania - Ustawienia - Natychmiastowe wysyłanie zostało kompletnie zmienione. Proszę sprawdzić główne menu i zrekonfigurować automatyczne wysyłanie. Przepraszamy za niedogodności.\n\nCiesz się z nowych i rozbudowanych możliwości automatycznego wysyłania! - Dla %1$s + Ładowanie folderów… + Nie znaleziono folderów multimedialnych. + Preferencje automatycznego wysyłania + Ustawienia + Natychmiastowe wysyłanie zostało kompletnie zmienione. Proszę sprawdzić główne menu i zrekonfigurować automatyczne wysyłanie. Przepraszamy za niedogodności.\n\nCiesz się z nowych i rozbudowanych możliwości automatycznego wysyłania! + Dla %1$s %d zaznaczony %d zaznaczone @@ -641,6 +641,6 @@ Prywatność Nie odnaleziono pliku - Skonfiguruj foldery + Skonfiguruj foldery diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 6444026241d4..534ea5f73b15 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -362,8 +362,8 @@ %1$s não pôde ser copiado para pasta local %2$s Pasta de envio automático - Pasta local - Pasta remota + Pasta local + Pasta remota Usar subpastas Armazena em subpastas baseado no ano e mês @@ -557,7 +557,7 @@ Pesquisar Este é um recurso do Nextcloud. Por favor, atualize. Saiba mais - Envio automático + Envio automático Participar Ajuda para teste Encontrou um erro? Comentários? @@ -577,12 +577,12 @@ Mover para… Copiar para… Escolha a pasta… - Carregando pastas… - Nenhuma pasta de mídia encontrada. - Preferências do auto envio - Configurações - O envio automático foi completamente renovado. Reconfigure-o dentro do menu principal.\n\nCurta o novo e estendido envio automático. - Para %1$s + Carregando pastas… + Nenhuma pasta de mídia encontrada. + Preferências do auto envio + Configurações + O envio automático foi completamente renovado. Reconfigure-o dentro do menu principal.\n\nCurta o novo e estendido envio automático. + Para %1$s %d selecionado %d selecionados @@ -665,7 +665,7 @@ Privacidade Arquivo não encontrado! - Configurar pastas + Configurar pastas Teste de conexão ao servidor diff --git a/src/main/res/values-pt-rPT/strings.xml b/src/main/res/values-pt-rPT/strings.xml index da15c58a91c5..f3606c454107 100644 --- a/src/main/res/values-pt-rPT/strings.xml +++ b/src/main/res/values-pt-rPT/strings.xml @@ -348,10 +348,16 @@ Quer realmente remover os itens selecionados e os seus conteúdos? Procura Conheça melhor - Carregar automaticamente + Carregar automaticamente Participe - Dossiês multimédia não encontrados - Parametros + Ajude-nos a testar + Encontrou um erro? Algo está a funcionar estranhamente? + Reportar um problema no Github + Interessado em nos ajudar a testar a próxima versão? + Dossiês multimédia não encontrados + Carregar preferências automaticamente + Parametros + Introduza o nome e tipo de ficheiro a ser carregado Nome do ficheiro Tipo de ficheiro Atalhos(%s) de Internet diff --git a/src/main/res/values-ro/strings.xml b/src/main/res/values-ro/strings.xml index 9a85fdf4461a..4dd0a8b430e4 100644 --- a/src/main/res/values-ro/strings.xml +++ b/src/main/res/values-ro/strings.xml @@ -256,8 +256,8 @@ Previzualizare imagine %1$s nu a putut fi copiat in dosarul local %2$s Dosar pentru încărcare instantă - Dosar local - Dosar la distanță + Dosar local + Dosar la distanță Folosește subdosare Stocați in subdosar bazat pe an și lună @@ -392,17 +392,19 @@ Caută Aceasta este o funcționalitate Nextcloud, vă rugăm să actulizați aplicația. Află mai mult - Încărcare automată + Încărcare automată Participă Candidatul pentru lansare Contribuie în mod activ Mută în… Copiază în… Alege dosar… - Încărcare dosare… - Nu au fost găsite dosare media. - Setări - Pentru %1$s + Încărcare dosare… + Nu au fost găsite dosare media. + Preferințe pentru încărcare automată + Setări + Funcționalitatea de încărcare automată a fost refăcută complet. Vă rugăm să accesați meniul principal și să refaceți setările de încărcare automată. Ne cerem scuze pentru inconveniență.\n\nBucură-te de noile capabilități extinse ale funcției de încărcare automată! + Pentru %1$s %d selectat %d selectate diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index fb213f8a0fd4..b429833710d6 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -364,8 +364,8 @@ %1$s невозможно скопировать в локальный каталог %2$s Каталог для немедленной загрузки - Локальный каталог - Каталог на сервере + Локальный каталог + Каталог на сервере Использовать подкаталоги Хранить в них, учитывая год и месяц @@ -559,7 +559,7 @@ Поиск Это есть в новой версии Nextcloud, пожалуйста обновитесь Узнать больше - Автоматическая загрузка + Автоматическая загрузка Участие Помогите нам в тестировании Нашли ошибку? Заметили необычное поведение программы? @@ -579,12 +579,12 @@ Переместить в… Копировать в… Выберите каталог … - Загрузка каталогов … - Не найдены каталоги с медиа(файлами). - Настройки автозагрузки - Настройки - Моментальная загрузка была полностью переделана. Используйте главное меню для перенастройки автоматической загрузки. Извините за неудобства.\n\nВам понравятся новые расширенные возможности автоматической загрузки! - Для %1$s + Загрузка каталогов … + Не найдены каталоги с медиа(файлами). + Настройки автозагрузки + Настройки + Моментальная загрузка была полностью переделана. Используйте главное меню для перенастройки автоматической загрузки. Извините за неудобства.\n\nВам понравятся новые расширенные возможности автоматической загрузки! + Для %1$s %d выбран %d выбрано @@ -669,7 +669,7 @@ Конфиденциальность Файл не найден! - Настроить каталоги + Настроить каталоги Проверить соединение с сервером diff --git a/src/main/res/values-sk-rSK/strings.xml b/src/main/res/values-sk-rSK/strings.xml index abe0739d72e9..5ec46a8b7b20 100644 --- a/src/main/res/values-sk-rSK/strings.xml +++ b/src/main/res/values-sk-rSK/strings.xml @@ -247,8 +247,8 @@ Ukážka obrazu %1$s nemožno skopírovať do lokálneho priečinka %2$s Priečinok pre okamžité nahratie - Miestny priečinok - Vzdialený priečinok + Miestny priečinok + Vzdialený priečinok Použi podpriečinky Ulož v podpriečinkoch podľa roku a mesiaca @@ -383,17 +383,19 @@ Hľadať Toto je funkcia Nextcloudu, aktualizujte prosím. Dozvedieť sa viac - Automatické nahratie + Automatické nahratie Zúčastniť sa Release candidate Aktívne prispievať Presunúť do… Kopírovať do… Vybrať priečinok… - Načítavanie priečinkov… - Neboli nájdené žiadne multimediálne priečinky. - Nastavenia - Pre %1$s + Načítavanie priečinkov… + Neboli nájdené žiadne multimediálne priečinky. + Nastavenia automatického nahratia + Nastavenia + Okamžité nahrávanie bolo kompletne prepracované. Prezrite si prosím hlavné menu a znovu nastavte automatické nahrávanie. Ospravedlňujeme sa za vzniknuté problémy.\n\nUžite si nové, rozšírene možnosti automatického nahrávania! + Pre %1$s %d vybraný %d vybraných diff --git a/src/main/res/values-sq/strings.xml b/src/main/res/values-sq/strings.xml index 45576d20bfcc..9f53cc352063 100644 --- a/src/main/res/values-sq/strings.xml +++ b/src/main/res/values-sq/strings.xml @@ -362,8 +362,8 @@ %1$s s\’u kopjua dot te dosja vendore %2$s Dosja e ngarkimit të çastit - Dosje vendore - Dosje e largët + Dosje vendore + Dosje e largët Përdorni nëndosjet Ruani në nëndosjet në bazë të vitit dhe muajit @@ -557,7 +557,7 @@ Kërkoni Ky është një tipar i Nextcloud-it, ju lutemi përditësojeni. Mësoni më shumë - Ngarkim automatik + Ngarkim automatik Merni pjesë Ndihmo duke testuar Gjetet një defekt? Diçka është e çuditshme? @@ -577,12 +577,12 @@ Levizni tek… Kopjojeni tek… Zgjidh dosjen… - Duke ngarkuar dosjet… - Nuk u gjetën dosje media - Preferencat për ngarkimin automatik - Konfigurimet - Ngarkimi i menjëhershëm është ngritur plotësisht. Ju lutemi shikoni menun kryesore dhe ri-konfiguroni ngarkimet tuaja automatike. Ju kërkojmë ndjes për shqetësimin.\n\nShijojini aftësitë e reja dhe të zgjeruara të ngarkimit automatik. - Për %1$s + Duke ngarkuar dosjet… + Nuk u gjetën dosje media + Ngarko automatikisht preferencat + Konfigurimet + Ngarkimi i menjëhershëm është ngritur plotësisht. Ju lutemi shikoni menun kryesore dhe ri-konfiguroni ngarkimet tuaja automatike. Ju kërkojmë ndjes për shqetësimin.\n\nShijojini aftësitë e reja dhe të zgjeruara të ngarkimit automatik. + Për %1$s %d të përzgjedhura %d të përzgjedhura @@ -665,7 +665,7 @@ Privatësi Skedari nuk u gjet! - Konfiguro dosjet + Konfiguro dosjet Testo lidhjen e serverit diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index 7cdc6b909a1c..62b80a19d652 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -248,8 +248,8 @@ Förhandsvisa bild %1$s kunde inte kopieras till %2$s lokal mapp Sökväg för Automatisk uppladdning - Lokal mapp - Extern mapp + Lokal mapp + Extern mapp Använd undermappar Spara i undermappar baserat på år och månad @@ -385,17 +385,19 @@ Sök Detta är en Nextcloud funktion, vänligen uppdatera. Lär dig mer - Automatisk uppladdning + Automatisk uppladdning Hjälp oss att bli bättre Release candidate Bidra aktivt Fllytta till… Kopiera till… Välj mapp… - Laddar mappar… - Inga mediamappar hittades - Inställningar - Mapp på telefonen: %1$s + Laddar mappar… + Inga mediamappar hittades + Aktivera Automatisk uppladdning + Inställningar + Direktuppladdning heter numera Automatisk uppladdning och har blivit totalt omgjort. Vänligen se huvudmenyn för att konfigurera Automatisk uppladdning igen. Ledsen för besväret.\n\nNjut av de nya och förbättrade uppladdningsmöjligheterna! + Mapp på telefonen: %1$s %d vald %d vald diff --git a/src/main/res/values-tr/strings.xml b/src/main/res/values-tr/strings.xml index 6daaa915de8e..46c6e06361cc 100644 --- a/src/main/res/values-tr/strings.xml +++ b/src/main/res/values-tr/strings.xml @@ -362,8 +362,8 @@ %1$s, %2$s yerel klasörüne kopyalanamadı Anında yükleme klasörü - Yerel klasör - Uzak klasör + Yerel klasör + Uzak klasör Alt klasörler kullanılsın Yıla ve aya göre alt klasörlere kaydedilsin @@ -557,7 +557,7 @@ Arama Bu bir Nextcloud özelliğidir, lütfen güncelleyin. Ayrıntılı bilgi alın - Otomatik yükleme + Otomatik yükleme Katkıda bulunun Denememize yardımcı olun Bir hata mı buldunuz? Bir gariplik mi var? @@ -577,12 +577,12 @@ Taşı… Kopyala… Klasör seçin… - Klasörler yükleniyor… - Herhangi bir ortam klasörü bulunamadı. - Otomatik yükleme ayarları - Ayarlar - Anında yükleme tamamen elden geçirildi. Lütfen ama menüye giderek otomatik yükleme ayarlarınızı yeniden yapın.\n\nYeni ve geliştirilmiş otomatik yükleme özellklerinin tadını çıkarın. - %1$s için + Klasörler yükleniyor… + Herhangi bir ortam klasörü bulunamadı. + Otomatik yükleme ayarları + Ayarlar + Anında yükleme tamamen elden geçirildi. Lütfen ama menüye giderek otomatik yükleme ayarlarınızı yeniden yapın.\n\nYeni ve geliştirilmiş otomatik yükleme özellklerinin tadını çıkarın. + %1$s için %d seçilmiş %d seçilmiş @@ -665,7 +665,7 @@ Gizlilik Dosya bulunamadı! - Klasör ayarları + Klasör ayarları Sunucu bağlantısını sına diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index dfa9eab1a24a..c93d31df97a1 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -334,9 +334,6 @@ Перемістити до… Копіювати до… Оберіть каталог… - Завантаження каталогів… - Налаштування - Для %1$s %d обрано %d обрано diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 52c4586adc7d..a17c0f7be7a5 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -365,8 +365,8 @@ 无法复制 %1$s 到本地目录 %2$s 实时上传文件夹 - 本地文件夹 - 远端文件夹 + 本地文件夹 + 远端文件夹 使用子文件夹 基于年和月存于子文件夹 @@ -560,7 +560,7 @@ 搜索 这是一个Nextcloud的特征,请更新 学习更多 - 自动上传 + 自动上传 参加 通过测试帮助 发现错误?细节? @@ -580,12 +580,12 @@ 转移到… 复制到 选择文件夹… - 加载文件夹… - 没有发现媒体文件夹 - 自动上传的首选项 - 设置 - 即时上传已完全重新整理。 从主菜单重新配置您的自动上传。\ n \ n请使用新的的和扩展的自动上传。 - 为%1$s + 加载文件夹… + 没有发现媒体文件夹 + 自动上传的首选项 + 设置 + 即时上传已完全重新整理。 从主菜单重新配置您的自动上传。\ n \ n请使用新的的和扩展的自动上传。 + 为%1$s %d被选择 @@ -667,7 +667,7 @@ 隐私 文件未找到 - 配置文件夹 + 配置文件夹 测试服务连接 diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 364cbe4fc7a9..81cf17adcfd3 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -362,8 +362,6 @@ %1$s 無法被複製到本地資料夾 %2$s 即時上傳資料夾 - 本地資料夾 - 遠端資料夾 使用子資料夾 依據年月存在子資料夾內 @@ -558,7 +556,6 @@ 搜尋 這是 Nextcloud 的功能,請更新 瞭解更多 - 自動上傳 參與 協助測試 發現問題或瑕疵? diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index 14f140ef4d89..690a57806a18 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -27,11 +27,13 @@ #fafafa #B2000000 @color/black + #ff888888 #eee #DDDDDD #EEEEEE #00000000 #a0a0a0 + #e53935 #757575 diff --git a/src/main/res/values/dims.xml b/src/main/res/values/dims.xml index b544c2005869..a3556ead6caa 100644 --- a/src/main/res/values/dims.xml +++ b/src/main/res/values/dims.xml @@ -18,9 +18,6 @@ --> - @dimen/standard_padding - @dimen/standard_padding - 260dp 140dp 56dp @@ -71,25 +68,18 @@ 14sp 12sp 2dp - 2dp 12dp 20dp 15dp 40dp 240dp - 15dp - 30dp 16dip 100dp 2dp 16dp - 22dp 32dp 12dp 64dp - 16dip - -4dip - 40dp 200dp 20dp 12sp diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index d77634e709c0..abbebe0f1469 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ Passcode lock Fingerprint lock No fingerprints have been set up. + Expert mode Show hidden files Instant uploading of pictures Upload pictures taken by camera instantly @@ -80,6 +81,7 @@ Files Connect Upload + Choose Choose upload folder No account found There are no %1$s accounts on your device. Please set up an account first. @@ -98,6 +100,7 @@ Delete file from source folder seconds ago No files here + No folders here Upload some content or sync with your devices. Favorite some files or sync with your devices. Files and folders you mark as favorites will show up here @@ -105,6 +108,7 @@ Loading… No app set up to handle this file type. There are no files in this folder. + There are no further folders. No results in this folder No results Nothing favorited yet @@ -153,11 +157,14 @@ unknown Unknown error Pending + Delete About Change password Remove account Delete account %s with all local files?\n\nDeleting cannot be undone. Create account + Avatar + Active user Upload from … Folder name Uploading … @@ -169,7 +176,7 @@ Upload failed, you need to log in again Uploads Current - Failed (tap to retry) + Failed / Pending restart Uploaded Completed Cancelled @@ -363,8 +370,8 @@ %1$s could not be copied to %2$s local folder Instant upload folder - Local folder - Remote folder + Local folder + Remote folder Use subfolders Store in subfolders based on year and month @@ -536,10 +543,11 @@ Stop sharing done - New attempt failed - Clear failed + Retry failed uploads + Clear failed uploads Clear successful uploads Clear finished uploads + Force rescan Grid view List view @@ -554,12 +562,13 @@ Do you really want to remove the selected items? Do you really want to remove the selected items and their contents? Server in maintenance mode + Failed to lock the file Awaiting charge Search This is a Nextcloud feature, please update. Learn more - Auto upload + Auto upload Participate Help by testing Found a bug? Oddments? @@ -582,13 +591,15 @@ <font color=\"%1$s\"><a href=\"%2$s\">CONTRIBUTING.md</a></font> Move to… Copy to… - Choose folder… - Loading folders… - No media folders found. - Preferences for auto uploading - Settings - Instant uploading has been revamped completely. Re-configure your auto upload from within the main menu.\n\nEnjoy the new and extended auto uploading. - For %1$s + Choose remote folder… + Choose local folder… + Loading folders… + No media folders found. + Preferences for auto uploading + Settings + Instant uploading has been revamped completely. Re-configure your auto upload from within the main menu.\n\nEnjoy the new and extended auto uploading. + For %1$s + + + Set up a custom folder + Create new custom folder setup + Configure folders - + Test server connection + , diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index 6f9d60d1aa96..2cade420505a 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -116,7 +116,11 @@ + + +