This commit is contained in:
Andy Scherzinger 2017-08-08 11:24:11 +00:00 committed by GitHub
commit 7f9f7e2526
314 changed files with 4375 additions and 3292 deletions

View file

@ -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 {

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M20,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8A2,2 0 0,0 20,6M17.94,17L15,15.28L12.06,17L12.84,13.67L10.25,11.43L13.66,11.14L15,8L16.34,11.14L19.75,11.43L17.16,13.67L17.94,17Z" /></svg>

After

Width:  |  Height:  |  Size: 492 B

1
gradle.properties Normal file
View file

@ -0,0 +1 @@
android.enableAapt2=false

View file

@ -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

View file

@ -2,5 +2,6 @@
<lint>
<issue id="InvalidPackage">
<ignore path="**/freemarker-2.3.23.jar"/>
<ignore path="**/nnio-0.2.jar"/>
</issue>
</lint>

View file

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 10 errors and 676 warnings</span>
<span class="mdl-layout-title">Lint Report: 6 errors and 653 warnings</span>

View file

@ -167,7 +167,7 @@ public class PushUtils {
remoteOperationResult = unregisterAccountDeviceForProxyOperation.execute(mClient);
if (remoteOperationResult.isSuccess()) {
arbitraryDataProvider.deleteKeyForAccount(account, KEY_PUSH);
arbitraryDataProvider.deleteKeyForAccount(account.name, KEY_PUSH);
}
}
}
@ -244,7 +244,7 @@ public class PushUtils {
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));
}
}

View file

@ -86,7 +86,7 @@
<activity android:name=".ui.activity.ParticipateActivity" />
<activity android:name=".ui.activity.ActivitiesListActivity"
android:configChanges="orientation|screenSize|keyboardHidden" />
<activity android:name=".ui.activity.FolderSyncActivity" />
<activity android:name=".ui.activity.SyncedFoldersActivity"/>
<activity android:name=".ui.activity.UploadFilesActivity" />
<activity android:name=".ui.activity.ExternalSiteWebView"
android:configChanges="orientation|screenSize|keyboardHidden" />
@ -120,6 +120,11 @@
android:label="@string/app_name"
android:theme="@style/Theme.ownCloud.Fullscreen" />
<service
android:name=".jobs.NContentObserverJob"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<service
android:name=".authentication.AccountAuthenticatorService"
android:exported="true" >
@ -131,7 +136,7 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<service android:name=".services.observer.SyncedFolderObserverService"/>
<service
android:name=".syncadapter.FileSyncService"
android:exported="true" >
@ -160,7 +165,7 @@
android:label="@string/search_users_and_groups_hint" />
<provider
android:name="org.nextcloud.providers.DocumentsStorageProvider"
android:name=".providers.DocumentsStorageProvider"
android:authorities="@string/document_provider_authority"
android:exported="true"
android:grantUriPermissions="true"
@ -226,43 +231,12 @@
<activity android:name=".ui.activity.UploadListActivity" />
<activity android:name=".ui.activity.WhatsNewActivity"
android:theme="@style/Theme.ownCloud.noActionBar.Login" />
<receiver android:name=".files.services.ConnectivityActionReceiver"
android:enabled="true" android:label="ConnectivityActionReceiver">
<intent-filter>
<!--action android:name="android.net.conn.CONNECTIVITY_CHANGE"/-->
<action android:name="android.net.wifi.STATE_CHANGE"/>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>
<receiver android:name=".files.InstantUploadBroadcastReceiver">
<intent-filter>
<!-- unofficially supported by many Android phones but not by HTC devices: -->
<action android:name="com.android.camera.NEW_PICTURE" />
<!-- officially supported since Android 4.0 (SDK 14, works even for HTC devices): -->
<action android:name="android.hardware.action.NEW_PICTURE" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.action.NEW_VIDEO" />
<data android:mimeType="video/*" />
</intent-filter>
</receiver>
<receiver android:name=".files.BootupBroadcastReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".services.ShutdownReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
<action android:name="android.intent.action.QUICKBOOT_POWEROFF" />
</intent-filter>
</receiver>
<service android:name=".services.observer.FileObserverService" />

View file

@ -35,7 +35,6 @@ public abstract class SectionedRecyclerViewAdapter<VH extends RecyclerView.ViewH
private final ArrayMap<Integer, Integer> mHeaderLocationMap;
private GridLayoutManager mLayoutManager;
private ArrayMap<Integer, Integer> mSpanMap;
private boolean mShowHeadersForEmptySections;
public SectionedRecyclerViewAdapter() {

View file

@ -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 class MainApp extends MultiDexApplication {
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 class MainApp extends MultiDexApplication {
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 class MainApp extends MultiDexApplication {
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 @@ public class MainApp extends MultiDexApplication {
}
}
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<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null);
final List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1);
ArrayList<Long> idsToDelete = new ArrayList<>();
List<SyncedFolder> 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 @@ public class MainApp extends MultiDexApplication {
}
}
for (Long idValue : syncedFolders.values()) {
ids.add(idValue);
}
ids.addAll(syncedFolders.values());
if (ids.size() > 0) {
syncedFolderProvider.deleteSyncedFoldersNotInList(mContext, ids);
@ -313,24 +438,4 @@ public class MainApp extends MultiDexApplication {
}
}
}
/**
* 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;
}
};
}

View file

@ -31,6 +31,7 @@ import android.view.WindowManager;
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 @@ public class PassCodeManager {
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);
}
}

View file

@ -47,36 +47,43 @@ public class ArbitraryDataProvider {
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<String> 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 class ArbitraryDataProvider {
);
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 class ArbitraryDataProvider {
return getIntegerValue(account.name, key);
}
private ArrayList<String> 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<String> 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 class ArbitraryDataProvider {
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
);

View file

@ -593,7 +593,6 @@ public class FileDataStorageManager {
if (localFile.isDirectory()) {
success &= removeLocalFolder(localFile);
} else {
String path = localFile.getAbsolutePath();
success &= localFile.delete();
}
}
@ -602,7 +601,6 @@ public class FileDataStorageManager {
return success;
}
/**
* Updates database and file system for a file or folder that was moved to a different location.
*

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> getFilesForUpload(String localPath, String syncedFolderId) {
Set<String> 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;
}
}

View file

@ -4,17 +4,17 @@
* @author Andy Scherzinger
* Copyright (C) 2016 Andy Scherzinger
* Copyright (C) 2016 Nextcloud
* <p>
*
* 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.
* <p>
*
* 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.
* <p>
*
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@ -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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<MediaFolderType> 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;
}
}

View file

@ -40,6 +40,8 @@ import java.io.File;
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<MediaFolder> getMediaFolders(ContentResolver contentResolver, int itemLimit,
final Activity activity) {
public static List<MediaFolder> 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<MediaFolder> 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 class MediaProvider {
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<MediaFolder> getVideoFolders(ContentResolver contentResolver, int itemLimit) {
Cursor cursorFolders = contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
VIDEOS_FOLDER_PROJECTION, null, null, null);
List<MediaFolder> 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 class MediaProvider {
// 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();
}

View file

@ -26,7 +26,7 @@ import java.io.Serializable;
/**
* 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 class SyncedFolder implements Serializable {
this.account = account;
this.uploadAction = uploadAction;
this.enabled = enabled;
this.type = type;
}
/**
@ -76,9 +80,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(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 class SyncedFolder implements Serializable {
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 class SyncedFolder implements Serializable {
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public MediaFolderType getType() {
return type;
}
public void setType(MediaFolderType type) {
this.type = type;
}
}

View file

@ -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<String> filePaths, String folderName, long numberOfFiles) {
super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled);
List<String> 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 class SyncedFolderDisplayItem extends SyncedFolder {
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<String> getFilePaths() {
return filePaths;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.datamodel;
@ -27,7 +28,6 @@ import android.database.Cursor;
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 class SyncedFolderProvider extends Observable {
}
/**
* 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 class SyncedFolderProvider extends Observable {
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 class SyncedFolderProvider extends Observable {
/**
* 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 class SyncedFolderProvider extends Observable {
}
return result;
}
/**
@ -211,15 +209,12 @@ public class SyncedFolderProvider extends Observable {
*
* @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 class SyncedFolderProvider extends Observable {
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 class SyncedFolderProvider extends Observable {
new String[]{String.valueOf(syncedFolder.getId())}
);
if (result > 0) {
notifyFolderSyncObservers(syncedFolder);
}
return result;
}
@ -325,9 +327,11 @@ public class SyncedFolderProvider extends Observable {
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 @@ public class SyncedFolderProvider extends Observable {
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());
cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().getId());
return cv;
}
/**
* 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());
}
}
}

View file

@ -30,6 +30,7 @@ import android.graphics.Canvas;
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 @@ public class ThumbnailsCacheManager {
}
public static class MediaThumbnailGenerationTask extends AsyncTask<Object, Void, Bitmap> {
private enum Type {IMAGE, VIDEO}
private final WeakReference<ImageView> mImageViewReference;
private File mFile;
private String mImageKey = null;
@ -439,7 +441,9 @@ public class ThumbnailsCacheManager {
}
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 @@ public class ThumbnailsCacheManager {
}
}
private Bitmap doFileInBackground(File file) {
private Bitmap doFileInBackground(File file, Type type) {
final String imageKey;
if (mImageKey != null) {
@ -497,14 +501,45 @@ public class ThumbnailsCacheManager {
// 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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.datamodel;
@ -26,12 +26,8 @@ import android.content.ContentValues;
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.files.services.FileUploader;
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 class UploadsStorageManager extends Observable {
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 class UploadsStorageManager extends Observable {
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 @@ public class UploadsStorageManager extends Observable {
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 @@ public class UploadsStorageManager extends Observable {
* @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 class UploadsStorageManager extends Observable {
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 class UploadsStorageManager extends Observable {
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 class UploadsStorageManager extends Observable {
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 @@ public class UploadsStorageManager extends Observable {
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<OCUpload> result = getPendingJobs();
Collections.addAll(result, uploads);
return result.toArray(uploads);
}
}
return uploads;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private List<OCUpload> getPendingJobs() {
Set<JobRequest> jobRequests = JobManager.create(mContext).getAllJobRequestsForTag(AutoUploadJob.TAG);
ArrayList<OCUpload> 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;
}
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<JobRequest> 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 class UploadsStorageManager extends Observable {
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 class UploadsStorageManager extends Observable {
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 @@ public class UploadsStorageManager extends Observable {
}
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 class UploadsStorageManager extends Observable {
}
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 class UploadsStorageManager extends Observable {
}
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 class UploadsStorageManager extends Observable {
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 class UploadsStorageManager extends Observable {
*/
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 class UploadsStorageManager extends Observable {
* 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 class UploadsStorageManager extends Observable {
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 class UploadsStorageManager extends Observable {
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});
}
}

View file

@ -40,7 +40,6 @@ import java.io.File;
/**
* 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 class OCUpload implements Parcelable {
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 class OCUpload implements Parcelable {
this(ocFile.getStoragePath(), ocFile.getRemotePath(), account.name);
}
/**
* Reset all the fields to default values.
*/
@ -151,6 +160,8 @@ public class OCUpload implements Parcelable {
mUploadStatus = UploadStatus.UPLOAD_IN_PROGRESS;
mLastResult = UploadResult.UNKNOWN;
mCreatedBy = UploadFileOperation.CREATED_BY_USER;
mIsUseWifiOnly = true;
mIsWhileChargingOnly = false;
}
// Getters & Setters
@ -230,7 +241,6 @@ public class OCUpload implements Parcelable {
mFileSize = fileSize;
}
/**
* @return the mimeType
*/
@ -340,6 +350,28 @@ public class OCUpload implements Parcelable {
}
};
/**
* @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 class OCUpload implements Parcelable {
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 class OCUpload implements Parcelable {
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}

View file

@ -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 abstract class PreferenceManager {
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 abstract class PreferenceManager {
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 abstract class PreferenceManager {
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 abstract class PreferenceManager {
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 abstract class PreferenceManager {
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 @@ public abstract class PreferenceManager {
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());
}

View file

@ -32,7 +32,7 @@ import com.owncloud.android.MainApp;
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 @@ public class ProviderMeta {
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 @@ public class ProviderMeta {
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 @@ public class ProviderMeta {
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 @@ public class ProviderMeta {
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 @@ public class ProviderMeta {
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";
}
}

View file

@ -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 enum UploadResult {
return DELAYED_FOR_CHARGING;
case 12:
return MAINTENANCE_MODE;
case 13:
return LOCK_FAILED;
}
return null;
}
@ -120,6 +123,8 @@ public enum UploadResult {
return UNKNOWN;
case MAINTENANCE_MODE:
return MAINTENANCE_MODE;
case LOCK_FAILED:
return LOCK_FAILED;
default:
return UNKNOWN;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
*/
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 = "<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 "<unknown 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);
}
*/
}

View file

@ -34,7 +34,6 @@ import android.app.Service;
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.os.Process;
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.Iterator;
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 class FileUploader extends Service
sendBroadcastUploadStarted(mCurrentUpload);
}
/**
* Helper class providing methods to ease requesting commands to {@link FileUploader} .
*
@ -214,7 +220,9 @@ public class FileUploader extends Service
String[] mimeTypes,
Integer behaviour,
Boolean createRemoteFolder,
int createdBy
int createdBy,
boolean requiresWifi,
boolean requiresCharging
) {
Intent intent = new Intent(context, FileUploader.class);
@ -225,15 +233,69 @@ public class FileUploader extends Service
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);
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);
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 class FileUploader extends Service
new String[]{mimeType},
behaviour,
createRemoteFile,
createdBy
createdBy,
requiresWifi,
requiresCharging
);
}
@ -288,7 +352,6 @@ public class FileUploader extends Service
}
}
/**
* Retry a subset of all the stored failed uploads.
*
@ -306,7 +369,7 @@ public class FileUploader extends Service
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 @@ public class FileUploader extends Service
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 class FileUploader extends Service
am.addOnAccountsUpdatedListener(this, null, false);
}
/**
* Service clean-up when restarted after being killed
*/
@ -379,7 +440,6 @@ public class FileUploader extends Service
mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
}
/**
* Service clean up
*/
@ -399,7 +459,6 @@ public class FileUploader extends Service
super.onDestroy();
}
/**
* Entry point to add one or several files to the queue of uploads.
*
@ -426,9 +485,14 @@ public class FileUploader extends Service
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 class FileUploader extends Service
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 class FileUploader extends Service
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 class FileUploader extends Service
chunked,
forceOverwrite,
localAction,
this
this,
onWifiOnly,
whileChargingOnly
);
newUpload.setCreatedBy(createdBy);
if (isCreateRemoteFolder) {
@ -561,6 +628,9 @@ public class FileUploader extends Service
}
OCUpload upload = intent.getParcelableExtra(KEY_RETRY_UPLOAD);
onWifiOnly = upload.isUseWifiOnly();
whileChargingOnly = upload.isWhileChargingOnly();
UploadFileOperation newUpload = new UploadFileOperation(
account,
null,
@ -568,7 +638,9 @@ public class FileUploader extends Service
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 class FileUploader extends Service
}
/**
* Binder to let client components to perform operations on the queue of
* uploads.
* <p/>
* 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 FileUploader extends Service
* Map of listeners that will be reported about progress of uploads from a
* {@link FileUploaderBinder} instance
*/
private Map<String, OnDatatransferProgressListener> mBoundListeners =
new HashMap<String, OnDatatransferProgressListener>();
private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<>();
/**
* Cancels a pending or current upload of a remote file.
@ -656,7 +725,7 @@ public class FileUploader extends Service
* @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 class FileUploader extends Service
* @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 class FileUploader extends Service
*
* @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<UploadFileOperation, String> removeResult =
mPendingUploads.remove(accountName, remotePath);
UploadFileOperation upload = removeResult.first;
@ -686,14 +757,17 @@ public class FileUploader extends Service
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 class FileUploader extends Service
return (mPendingUploads.contains(account.name, file.getRemotePath()));
}
public boolean isUploadingNow(OCUpload upload) {
return (
upload != null &&
@ -751,7 +824,6 @@ public class FileUploader extends Service
);
}
/**
* Adds a listener interested in the progress of the upload for a concrete file.
*
@ -771,7 +843,6 @@ public class FileUploader extends Service
mBoundListeners.put(targetKey, listener);
}
/**
* Adds a listener interested in the progress of the upload for a concrete file.
*
@ -789,7 +860,6 @@ public class FileUploader extends Service
mBoundListeners.put(targetKey, listener);
}
/**
* Removes a listener interested in the progress of the upload for a concrete file.
*
@ -811,7 +881,6 @@ public class FileUploader extends Service
}
}
/**
* Removes a listener interested in the progress of the upload for a concrete file.
*
@ -831,7 +900,6 @@ public class FileUploader extends Service
}
}
@Override
public void onTransferProgress(long progressRate, long totalTransferredSoFar,
long totalToTransfer, String fileName) {
@ -840,12 +908,24 @@ public class FileUploader extends Service
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.
* <p/>
*
* 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 @@ public class FileUploader extends Service
/**
* Upload worker. Performs the pending uploads in the order they were
* requested.
* <p/>
*
* Created with the Looper of a new thread, started in
* {@link FileUploader#onCreate()}.
*/
@ -958,7 +1038,7 @@ public class FileUploader extends Service
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 class FileUploader extends Service
notifyUploadResult(mCurrentUpload, uploadResult);
sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second);
}
// generate new Thumbnail
@ -997,8 +1076,7 @@ public class FileUploader extends Service
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 @@ public class FileUploader extends Service
} // 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 @@ public class FileUploader extends Service
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 @@ public class FileUploader extends Service
.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 @@ public class FileUploader extends Service
}
}
/**
* Sends a broadcast in order to the interested activities can update their
* view
@ -1144,7 +1219,6 @@ public class FileUploader extends Service
sendStickyBroadcast(start);
}
/**
* Sends a broadcast in order to the interested activities can update their
* view
@ -1208,5 +1282,4 @@ public class FileUploader extends Service
mPendingUploads.remove(account.name);
mUploadsStorageManager.removeUploads(account.name);
}
}

View file

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.services;
package com.owncloud.android.jobs;
import android.accounts.Account;
import android.accounts.AccountManager;
@ -76,7 +76,7 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
// 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 {

View file

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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.datamodel.OCFile;
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 @@ public class ContactsBackupJob extends Job {
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 @@ public class ContactsBackupJob extends Job {
FileUploader.LOCAL_BEHAVIOUR_MOVE,
null,
true,
UploadFileOperation.CREATED_BY_USER
UploadFileOperation.CREATED_BY_USER,
false,
false
);
} catch (Exception e) {

View file

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.services;
package com.owncloud.android.jobs;
import android.database.Cursor;
import android.net.Uri;

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View file

@ -18,7 +18,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View file

@ -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 class MoveFileOperation extends SyncOperation {
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result = null;
RemoteOperationResult result;
/// 1. check move validity
if (mTargetParentPath.startsWith(mSrcPath)) {

View file

@ -203,7 +203,7 @@ public class RefreshFolderOperation extends RemoteOperation {
if (result.isSuccess()) {
// request for the synchronization of KEPT-IN-SYNC file contents
startContentSynchronizations(mFilesToSyncContents, client);
startContentSynchronizations(mFilesToSyncContents);
}
}
@ -453,12 +453,9 @@ public class RefreshFolderOperation extends RemoteOperation {
* on.
*
* @param filesToSyncContents Synchronization operations to execute.
* @param client Interface to the remote ownCloud server.
*/
private void startContentSynchronizations(
List<SynchronizeFileOperation> filesToSyncContents, OwnCloudClient client
) {
RemoteOperationResult contentsResult = null;
private void startContentSynchronizations(List<SynchronizeFileOperation> filesToSyncContents) {
RemoteOperationResult contentsResult;
for (SynchronizeFileOperation op: filesToSyncContents) {
contentsResult = op.execute(mStorageManager, mContext); // async
if (!contentsResult.isSuccess()) {
@ -487,7 +484,7 @@ public class RefreshFolderOperation extends RemoteOperation {
* the operation.
*/
private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
RemoteOperationResult result = null;
RemoteOperationResult result;
// remote request
GetRemoteSharesForFileOperation operation =

View file

@ -469,19 +469,6 @@ public class SynchronizeFolderOperation extends SyncOperation {
}
}
/**
* 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}

View file

@ -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 <http://www.gnu.org/licenses/>.
* 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 <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.operations;
@ -24,11 +23,12 @@ import android.accounts.Account;
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.ReadRemoteFileOperation;
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 com.owncloud.android.utils.UriUtils;
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 class UploadFileOperation extends SyncOperation {
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 class UploadFileOperation extends SyncOperation {
} else {
mFile = file;
}
mOnWifiOnly = onWifiOnly;
mWhileChargingOnly = whileChargingOnly;
mRemotePath = upload.getRemotePath();
mChunked = chunked;
mForceOverwrite = forceOverwrite;
@ -182,6 +193,14 @@ public class UploadFileOperation extends SyncOperation {
mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
}
public boolean getIsWifiRequired() {
return mOnWifiOnly;
}
public boolean getIsChargingRequired() {
return mWhileChargingOnly;
}
public Account getAccount() {
return mAccount;
}
@ -237,7 +256,7 @@ public class UploadFileOperation extends SyncOperation {
}
}
public int getCreatedBy () {
public int getCreatedBy() {
return mCreatedBy;
}
@ -249,9 +268,10 @@ public class UploadFileOperation extends SyncOperation {
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 class UploadFileOperation extends SyncOperation {
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 class UploadFileOperation extends SyncOperation {
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 @@ public class UploadFileOperation extends SyncOperation {
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 @@ public class UploadFileOperation extends SyncOperation {
}
// 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 <OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
Iterator<OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
while (listener.hasNext()) {
mUploadOperation.addDatatransferProgressListener(listener.next());
}
@ -394,23 +416,77 @@ public class UploadFileOperation extends SyncOperation {
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 @@ public class UploadFileOperation extends SyncOperation {
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 @@ public class UploadFileOperation extends SyncOperation {
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 @@ public class UploadFileOperation extends SyncOperation {
/**
* 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 @@ public class UploadFileOperation extends SyncOperation {
suffix = " (" + count + ")";
if (pos >= 0) {
check = existsFile(wc, remotePath + suffix + "." + extension);
}
else {
} else {
check = existsFile(wc, remotePath + suffix);
}
count++;
@ -629,7 +670,7 @@ public class UploadFileOperation extends SyncOperation {
}
}
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 class UploadFileOperation extends SyncOperation {
* 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 @@ public class UploadFileOperation extends SyncOperation {
*
* 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 @@ public class UploadFileOperation extends SyncOperation {
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 @@ public class UploadFileOperation extends SyncOperation {
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 @@ public class UploadFileOperation extends SyncOperation {
// generate new Thumbnail
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), mAccount);
new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), mAccount);
task.execute(file);
}

View file

@ -18,7 +18,7 @@
*
*/
package org.nextcloud.providers;
package com.owncloud.android.providers;
import android.accounts.Account;
import android.annotation.TargetApi;

View file

@ -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 @@ public class FileContentProvider extends ContentProvider {
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 @@ public class FileContentProvider extends ContentProvider {
if (uploadId > 0) {
insertedUploadUri =
ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_UPLOADS, uploadId);
trimSuccessfulUploads(db);
} else {
throw new SQLException(ERROR + uri);
@ -365,6 +368,16 @@ public class FileContentProvider extends ContentProvider {
}
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 class FileContentProvider extends ContentProvider {
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 @@ public class FileContentProvider extends ContentProvider {
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 @@ public class FileContentProvider extends ContentProvider {
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 @@ public class FileContentProvider extends ContentProvider {
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 class FileContentProvider extends ContentProvider {
// Create virtual table
createVirtualTable(db);
// Create filesystem table
createFileSystemTable(db);
}
@Override
@ -669,14 +697,14 @@ public class FileContentProvider extends ContentProvider {
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 class FileContentProvider extends ContentProvider {
}
}
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 class FileContentProvider extends ContentProvider {
}
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 class FileContentProvider extends ContentProvider {
}
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 class FileContentProvider extends ContentProvider {
db.endTransaction();
}
}
if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
}
@ -937,7 +966,6 @@ public class FileContentProvider extends ContentProvider {
} finally {
db.endTransaction();
}
}
if (!upgraded) {
@ -1033,6 +1061,43 @@ public class FileContentProvider extends ContentProvider {
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 @@ public class FileContentProvider extends ContentProvider {
+ 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 @@ public class FileContentProvider extends ContentProvider {
+ 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 @@ public class FileContentProvider extends ContentProvider {
+ 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 @@ public class FileContentProvider extends ContentProvider {
+ 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 @@ public class FileContentProvider extends ContentProvider {
);
}
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

View file

@ -1,237 +0,0 @@
/**
* Nextcloud Android client application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic
* <p>
* 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.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<String, Runnable> 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();
}
}

View file

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}

View file

@ -678,7 +678,7 @@ public class OperationsService extends Service {
// 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

View file

@ -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<AdvancedFileAlterationListener> listeners = new CopyOnWriteArrayList<>();
private FileEntry rootEntry;
private FileFilter fileFilter;
private Comparator<File> 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<AdvancedFileAlterationListener> 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();
}
}

View file

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}
}

View file

@ -37,7 +37,7 @@ import com.evernote.android.job.util.support.PersistableBundleCompat;
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;

View file

@ -211,12 +211,6 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
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 abstract class DrawerActivity extends ToolbarActivity implements DisplayU
}
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 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
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);

View file

@ -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 @@ public class ExternalSiteWebView extends FileActivity {
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 class ExternalSiteWebView extends FileActivity {
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);
}
});

View file

@ -476,7 +476,7 @@ public abstract class FileActivity extends DrawerActivity
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);

View file

@ -282,9 +282,8 @@ public class FileDisplayActivity extends HookActivity
*/
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 @@ public class FileDisplayActivity extends HookActivity
// 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 class FileDisplayActivity extends HookActivity
dialog.dismiss();
}
})
.setIcon(R.drawable.nav_folder_sync)
.setIcon(R.drawable.nav_synced_folders)
.show();
}
}
@ -477,7 +476,7 @@ public class FileDisplayActivity extends HookActivity
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 @@ public class FileDisplayActivity extends HookActivity
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 {

View file

@ -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 @@ public class FingerprintActivity extends AppCompatActivity {
}
}
@RequiresApi(Build.VERSION_CODES.M)
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
private TextView text;

View file

@ -302,6 +302,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
retval = super.onOptionsItemSelected(item);
break;
}
return retval;
}

View file

@ -273,9 +273,7 @@ public class LogHistoryActivity extends ToolbarActivity {
*/
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);

View file

@ -50,8 +50,7 @@ import com.owncloud.android.files.services.FileDownloader;
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 @@ public class ManageAccountsActivity extends FileActivity
// 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 @@ public class ManageAccountsActivity extends FileActivity
// 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)

View file

@ -85,9 +85,11 @@ import java.io.IOException;
*/
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 class Preferences extends PreferenceActivity
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 class Preferences extends PreferenceActivity
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 class Preferences extends PreferenceActivity
}
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 class Preferences extends PreferenceActivity
});
} 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 class Preferences extends PreferenceActivity
}
}
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 class Preferences extends PreferenceActivity
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 class Preferences extends PreferenceActivity
}
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 class Preferences extends PreferenceActivity
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 class Preferences extends PreferenceActivity
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 @@ public class Preferences extends PreferenceActivity
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 @@ public class Preferences extends PreferenceActivity
.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 class Preferences extends PreferenceActivity
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 @@ public class Preferences extends PreferenceActivity
SharedPreferences appPrefs =
PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
mStoragePath = appPrefs.getString(PreferenceKeys.STORAGE_PATH, Environment.getExternalStorageDirectory()
.getAbsolutePath());
.getAbsolutePath());
String storageDescription = DataStorageProvider.getInstance().getStorageDescriptionByPath(mStoragePath);
mPrefStoragePath.setSummary(storageDescription);
}

View file

@ -913,7 +913,9 @@ public class ReceiveExternalFilesActivity extends FileActivity
FileUploader.LOCAL_BEHAVIOUR_COPY,
null,
true,
UploadFileOperation.CREATED_BY_USER
UploadFileOperation.CREATED_BY_USER,
false,
false
);
finish();
}

View file

@ -55,11 +55,9 @@ import com.owncloud.android.utils.GetShareWithUsersAsyncTask;
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 @@ public class ShareActivity extends FileActivity
ft.replace(R.id.share_fragment_container, fragment, TAG_SHARE_FRAGMENT);
ft.commit();
}
}
protected void onAccountSet(boolean stateWasRecovered) {
@ -144,10 +139,9 @@ public class ShareActivity extends FileActivity
);
}
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 @@ public class ShareActivity extends FileActivity
}
/**
* 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 @@ public class ShareActivity extends FileActivity
editShareFragment.isAdded()) {
editShareFragment.refreshUiFromDB();
}
}
/**
@ -300,7 +292,6 @@ public class ShareActivity extends FileActivity
return (EditShareFragment) getSupportFragmentManager().findFragmentByTag(TAG_EDIT_SHARE_FRAGMENT);
}
private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation,
RemoteOperationResult result) {
if (result.isSuccess()) {
@ -369,8 +360,5 @@ public class ShareActivity extends FileActivity
t.show();
}
}
}
}

View file

@ -23,56 +23,66 @@ package com.owncloud.android.ui.activity;
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<SyncedFolderDisplayItem> syncFolderItems;
private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment;
private boolean showSidebar = true;
private RelativeLayout mCustomFolderRelativeLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -100,13 +110,37 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
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 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
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 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
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 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
return;
}
setListShown(false);
final Handler mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
final List<MediaFolder> mediaFolders = MediaProvider.getMediaFolders(getContentResolver(),
perFolderMediaItemLimit, FolderSyncActivity.this);
List<SyncedFolder> syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders();
List<SyncedFolder> currentAccountSyncedFoldersList = new ArrayList<>();
Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(FolderSyncActivity.this);
for (SyncedFolder syncedFolder : syncedFolderArrayList) {
if (syncedFolder.getAccount().equals(currentAccount.name)) {
currentAccountSyncedFoldersList.add(syncedFolder);
}
}
final List<MediaFolder> mediaFolders = MediaProvider.getImageFolders(getContentResolver(),
perFolderMediaItemLimit, SyncedFoldersActivity.this);
mediaFolders.addAll(MediaProvider.getVideoFolders(getContentResolver(), perFolderMediaItemLimit));
syncFolderItems = sortSyncedFolderItems(mergeFolderData(currentAccountSyncedFoldersList,
mediaFolders));
mHandler.post(new TimerTask() {
@Override
public void run() {
mAdapter.setSyncFolderItems(syncFolderItems);
setListShown(true);
}
});
List<SyncedFolder> syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders();
List<SyncedFolder> currentAccountSyncedFoldersList = new ArrayList<>();
Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(SyncedFoldersActivity.this);
for (SyncedFolder syncedFolder : syncedFolderArrayList) {
if (syncedFolder.getAccount().equals(currentAccount.name)) {
currentAccountSyncedFoldersList.add(syncedFolder);
}
}).start();
}
List<SyncedFolderDisplayItem> syncFolderItems = sortSyncedFolderItems(
mergeFolderData(currentAccountSyncedFoldersList, mediaFolders));
mAdapter.setSyncFolderItems(syncFolderItems);
mAdapter.notifyDataSetChanged();
setListShown(true);
}
/**
@ -215,20 +241,23 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
Map<String, SyncedFolder> syncedFoldersMap = createSyncedFoldersMap(syncedFolders);
List<SyncedFolderDisplayItem> 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 class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
@NonNull
private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) {
File localFolder = new File(syncedFolder.getLocalPath());
File[] files = getFileList(localFolder);
List<String> filePaths = getDisplayFilePathList(files);
return new SyncedFolderDisplayItem(
syncedFolder.getId(),
syncedFolder.getLocalPath(),
@ -287,7 +321,10 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
syncedFolder.getAccount(),
syncedFolder.getUploadAction(),
syncedFolder.isEnabled(),
new File(syncedFolder.getLocalPath()).getName());
filePaths,
localFolder.getName(),
files.length,
syncedFolder.getType());
}
/**
@ -311,7 +348,8 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
syncedFolder.isEnabled(),
mediaFolder.filePaths,
mediaFolder.folderName,
mediaFolder.numberOfFiles);
mediaFolder.numberOfFiles,
mediaFolder.type);
}
/**
@ -334,7 +372,42 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
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<File>() {
public int compare(File f1, File f2) {
return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
}
});
} else {
files = new File[]{};
}
return files;
}
private List<String> getDisplayFilePathList(File[] files) {
List<String> 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 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
Map<String, SyncedFolder> 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 class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
result = super.onOptionsItemSelected(item);
break;
}
return result;
}
@ -409,15 +483,27 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
@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 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
&& 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());
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
getContentResolver());
if (syncedFolder.getId() == UNPERSISTED_ID) {
// newly set up folder sync config
long storedId = mSyncedFolderProvider.storeFolderSync(item);
// 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 {
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);
}
} else {
// existing synced folder setup to be updated
mSyncedFolderProvider.updateSyncFolder(item);
}
mSyncedFolderPreferencesDialogFragment = null;
if (dirty) {
mAdapter.setSyncFolderItem(syncedFolder.getSection(), item);
}
mSyncedFolderPreferencesDialogFragment = null;
}
@Override
@ -474,6 +600,12 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
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 class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
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);
}
}

View file

@ -27,6 +27,8 @@ import android.graphics.PorterDuff;
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<String> 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 class UploadFilesActivity extends FileActivity implements
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 class UploadFilesActivity extends FileActivity implements
// 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 class UploadFilesActivity extends FileActivity implements
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 class UploadFilesActivity extends FileActivity implements
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 class UploadFilesActivity extends FileActivity implements
}
return true;
}
@Override
public void onBackPressed() {
@ -308,9 +326,10 @@ public class UploadFilesActivity extends FileActivity implements
}
// 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 @@ public class UploadFilesActivity extends FileActivity implements
}
}
// Custom array adapter to override text colors
/**
* Custom array adapter to override text colors
*/
private class CustomArrayAdapter<T> extends ArrayAdapter<T> {
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 class UploadFilesActivity extends FileActivity implements
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 class UploadFilesActivity extends FileActivity implements
return v;
}
}
/**
@ -394,9 +412,11 @@ public class UploadFilesActivity extends FileActivity implements
*/
@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 class UploadFilesActivity extends FileActivity implements
return mCurrentDir;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isFolderPickerMode() {
return mLocalFolderPickerMode;
}
/**
* Performs corresponding action when user presses 'Cancel' or 'Upload' button
*
@ -432,7 +460,17 @@ public class UploadFilesActivity extends FileActivity implements
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 class UploadFilesActivity extends FileActivity implements
private class CheckAvailableSpaceTask extends AsyncTask<Boolean, Void, Boolean> {
/**
* Updates the UI before trying the movement
* Updates the UI before trying the movement.
*/
@Override
protected void onPreExecute () {

View file

@ -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.Context;
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.MenuItem;
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 class UploadListActivity extends FileActivity implements UploadListFragme
} 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 class UploadListActivity extends FileActivity implements UploadListFragme
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 class UploadListActivity extends FileActivity implements UploadListFragme
@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 class UploadListActivity extends FileActivity implements UploadListFragme
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 class UploadListActivity extends FileActivity implements UploadListFragme
} else {
// already updated -> just retry!
FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
requester.retryFailedUploads(this, account, UploadResult.CREDENTIAL_ERROR);
FilesSyncHelper.restartJobsIfNeeded();
}
} else {

View file

@ -63,7 +63,6 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
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 class UserInfoActivity extends FileActivity {
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 class UserInfoActivity extends FileActivity {
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());
}

View file

@ -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 <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.adapter;
@ -26,7 +26,6 @@ import android.graphics.Bitmap;
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.ImageButton;
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.ThumbnailsCacheManager;
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 class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
@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 class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
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 class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
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 class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
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 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
/// ... 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 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
}
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 class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
//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 class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
} 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 class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
* 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 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
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 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
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 class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
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
);
}

View file

@ -41,6 +41,7 @@ import com.owncloud.android.utils.FileStorageUtils;
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<File> mFilesAll = new Vector<File>();
private Vector<File> 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 class LocalFileListAdapter extends BaseAdapter implements FilterableListA
* @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<File>() {
@Override
@ -288,7 +295,6 @@ public class LocalFileListAdapter extends BaseAdapter implements FilterableListA
private int compareNames(File lhs, File rhs) {
return lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase());
}
});
mFiles = FileStorageUtils.sortLocalFolder(mFiles);
@ -317,6 +323,15 @@ public class LocalFileListAdapter extends BaseAdapter implements FilterableListA
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]);

View file

@ -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 <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.adapter;
@ -29,10 +29,12 @@ import android.view.ViewGroup;
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 @@ import java.util.List;
/**
* Adapter to display all auto-synced folders and/or instant upload media folders.
*/
public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAdapter.MainViewHolder> {
public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SyncedFolderAdapter.MainViewHolder> {
private final Context mContext;
private final int mGridWidth;
@ -53,19 +55,20 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
private final List<SyncedFolderDisplayItem> 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<SyncedFolderDisplayItem> syncFolderItems) {
mSyncFolderItems.clear();
mSyncFolderItems.addAll(syncFolderItems);
notifyDataSetChanged();
}
public void setSyncFolderItem(int location, SyncedFolderDisplayItem syncFolderItem) {
@ -73,6 +76,16 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
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 class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
}
}
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 class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
} 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 class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
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 class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
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 class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
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 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
}
private void setSyncButtonActiveIcon(ImageButton syncStatusButton, boolean enabled) {
if(enabled) {
if (enabled) {
syncStatusButton.setImageDrawable(ThemeUtils.tintDrawable(R.drawable.ic_cloud_sync_on,
ThemeUtils.primaryColor()));
} else {

View file

@ -228,7 +228,9 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
behaviour,
mimeType,
false, // do not create parent folder if not existent
UploadFileOperation.CREATED_BY_USER
UploadFileOperation.CREATED_BY_USER,
false,
false
);
}

View file

@ -47,8 +47,10 @@ public class LoadingDialog extends DialogFragment {
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

View file

@ -105,7 +105,7 @@ public class SortingOrderDialogFragment extends DialogFragment {
mView = inflater.inflate(R.layout.sorting_order_fragment, container, false);
setupDialogElements(mView);
setupListeners(mView);
setupListeners();
return mView;
}
@ -208,10 +208,8 @@ public class SortingOrderDialogFragment extends DialogFragment {
/**
* 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) {

View file

@ -25,6 +25,7 @@ import android.app.Dialog;
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.view.ViewGroup;
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 class SyncedFolderPreferencesDialogFragment extends DialogFragment {
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 class SyncedFolderPreferencesDialogFragment extends DialogFragment {
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 class SyncedFolderPreferencesDialogFragment extends DialogFragment {
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 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
// 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 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
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 class SyncedFolderPreferencesDialogFragment extends DialogFragment {
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 class SyncedFolderPreferencesDialogFragment extends DialogFragment {
}
});
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 class SyncedFolderPreferencesDialogFragment extends DialogFragment {
@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 class SyncedFolderPreferencesDialogFragment extends DialogFragment {
}
}
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

View file

@ -23,6 +23,7 @@ package com.owncloud.android.ui.dialog.parcel;
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 class SyncedFolderParcelable implements Parcelable {
mChargingOnly = syncedFolderDisplayItem.getChargingOnly();
mEnabled = syncedFolderDisplayItem.isEnabled();
mSubfolderByDate = syncedFolderDisplayItem.getSubfolderByDate();
mType = syncedFolderDisplayItem.getType();
mAccount = syncedFolderDisplayItem.getAccount();
mUploadAction = syncedFolderDisplayItem.getUploadAction();
mSection = section;
@ -68,6 +71,7 @@ public class SyncedFolderParcelable implements Parcelable {
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 class SyncedFolderParcelable implements Parcelable {
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 class SyncedFolderParcelable implements Parcelable {
this.mSubfolderByDate = mSubfolderByDate;
}
public MediaFolderType getType() {
return mType;
}
public void setType(MediaFolderType mType) {
this.mType = mType;
}
public Integer getUploadAction() {
return mUploadAction;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View file

@ -955,7 +955,7 @@ public class ExtendedListFragment extends Fragment
maxColumnSize = maxColumnSizePortrait;
}
if (mGridView.getNumColumns() > maxColumnSize) {
if (mGridView != null && mGridView.getNumColumns() > maxColumnSize) {
mGridView.setNumColumns(maxColumnSize);
mGridView.invalidateViews();
}

View file

@ -26,6 +26,8 @@ import android.os.Bundle;
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 class LocalFileListFragment extends ExtendedListFragment {
}
}
/**
* {@inheritDoc}
*/
@ -97,11 +98,19 @@ public class LocalFileListFragment extends ExtendedListFragment {
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 class LocalFileListFragment extends ExtendedListFragment {
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 class LocalFileListFragment extends ExtendedListFragment {
* @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 class LocalFileListFragment extends ExtendedListFragment {
*/
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();
}

View file

@ -67,7 +67,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.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;

View file

@ -49,9 +49,9 @@ import com.owncloud.android.R;
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 @@ public class ContactsBackupFragment extends FileFragment implements DatePickerDi
contactsPreferenceActivity.getAccount());
}
arbitraryDataProvider.storeOrUpdateKeyValue(account, PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
String.valueOf(bool));
}

View file

@ -176,7 +176,7 @@ public class FileOperationsHelper {
openFileWithIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
List<ResolveInfo> launchables = mFileActivity.getPackageManager().
queryIntentActivities(openFileWithIntent, PackageManager.GET_INTENT_FILTERS);
queryIntentActivities(openFileWithIntent, PackageManager.GET_RESOLVED_FILTER);
if (launchables != null && launchables.size() > 0) {
try {

View file

@ -166,7 +166,9 @@ public class UriUploader {
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
);
}

View file

@ -183,7 +183,7 @@ public class PreviewTextFragment extends FileFragment {
}
private void loadAndShowTextPreview() {
mTextLoadTask = new TextLoadAsyncTask(new WeakReference<TextView>(mTextPreview));
mTextLoadTask = new TextLoadAsyncTask(new WeakReference<>(mTextPreview));
mTextLoadTask.execute(getFile().getStoragePath());
}
@ -192,14 +192,12 @@ public class PreviewTextFragment extends FileFragment {
* Reads the file to preview and shows its contents. Too critical to be anonymous.
*/
private class TextLoadAsyncTask extends AsyncTask<Object, Void, StringWriter> {
private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT";
private final WeakReference<TextView> mTextViewReference;
private TextLoadAsyncTask(WeakReference<TextView> textView) {
mTextViewReference = textView;
}
@Override
protected void onPreExecute() {
// not used at the moment
@ -454,7 +452,7 @@ public class PreviewTextFragment extends FileFragment {
* @return 'True' if the file can be handled by the fragment.
*/
public static boolean canBePreviewed(OCFile file) {
final List<String> unsupportedTypes = new LinkedList<String>();
final List<String> unsupportedTypes = new LinkedList<>();
unsupportedTypes.add("text/richtext");
unsupportedTypes.add("text/rtf");
unsupportedTypes.add("text/vnd.abc");

View file

@ -1,4 +1,4 @@
/**
/*
* ownCloud Android client application
*
* @author David A. Velasco
@ -23,7 +23,7 @@ import android.graphics.Bitmap;
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 @@ public class BitmapUtils {
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 @@ public class BitmapUtils {
* @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 class BitmapUtils {
s /= 100f;
l /= 100f;
float q = 0;
float q;
if (l < 0.5) {
q = l * (1 + s);

View file

@ -389,6 +389,11 @@ public class DisplayUtils {
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;

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Path>() {
@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<JobInfo> 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());
}
}
}

View file

@ -147,7 +147,7 @@ public class MimeTypeUtil {
} 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 @@ public class MimeTypeUtil {
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 @@ public class MimeTypeUtil {
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);
}
/**

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View file

@ -37,6 +37,7 @@ import android.support.v4.content.ContextCompat;
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 class ThemeUtils {
}
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},

View file

@ -273,7 +273,9 @@ public class GridViewWithHeaderAndFooter extends GridView {
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);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 B

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 B

Some files were not shown because too many files have changed in this diff Show more