diff --git a/.drone.yml b/.drone.yml index c262805f3528..b00a744da8e6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,6 +1,6 @@ pipeline: test: - image: nextcloudci/android:android-7 + image: nextcloudci/android:android-14 commands: - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI -c 20M - emulator -avd test -no-window & diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e8c1a2842147..b13fa200a097 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -25,7 +25,7 @@ + android:targetSdkVersion="25" /> disable + show info - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && + // 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))) { diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index a405e7ef65f9..294439c806c6 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -21,6 +21,7 @@ package com.owncloud.android.ui.activity; +import android.accounts.Account; import android.content.Intent; import android.os.Bundle; import android.os.Handler; @@ -48,6 +49,7 @@ import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment; import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -130,7 +132,16 @@ private void load(final int perFolderMediaItemLimit) { public void run() { final List mediaFolders = MediaProvider.getMediaFolders(getContentResolver(), perFolderMediaItemLimit); - syncFolderItems = sortSyncedFolderItems(mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), + List syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders(); + List currentAccountSyncedFoldersList = new ArrayList(); + Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(FolderSyncActivity.this); + for (SyncedFolder syncedFolder : syncedFolderArrayList) { + if (syncedFolder.getAccount().equals(currentAccount.name)) { + currentAccountSyncedFoldersList.add(syncedFolder); + } + } + + syncFolderItems = sortSyncedFolderItems(mergeFolderData(currentAccountSyncedFoldersList, mediaFolders)); mHandler.post(new TimerTask() { @@ -157,15 +168,22 @@ private List mergeFolderData(List syncedF Map syncedFoldersMap = createSyncedFoldersMap(syncedFolders); List result = new ArrayList<>(); + for (MediaFolder mediaFolder : mediaFolders) { if (syncedFoldersMap.containsKey(mediaFolder.absolutePath)) { SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath); + syncedFoldersMap.remove(mediaFolder.absolutePath); result.add(createSyncedFolder(syncedFolder, mediaFolder)); } else { result.add(createSyncedFolderFromMediaFolder(mediaFolder)); } } + for (SyncedFolder syncedFolder : syncedFoldersMap.values()) { + SyncedFolderDisplayItem syncedFolderDisplayItem = createSyncedFolderWithoutMediaFolder(syncedFolder); + result.add(syncedFolderDisplayItem); + } + return result; } @@ -210,6 +228,21 @@ public int compare(SyncedFolderDisplayItem f1, SyncedFolderDisplayItem f2) { return syncFolderItemList; } + @NonNull + private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) { + return new SyncedFolderDisplayItem( + syncedFolder.getId(), + syncedFolder.getLocalPath(), + syncedFolder.getRemotePath(), + syncedFolder.getWifiOnly(), + syncedFolder.getChargingOnly(), + syncedFolder.getSubfolderByDate(), + syncedFolder.getAccount(), + syncedFolder.getUploadAction(), + syncedFolder.isEnabled(), + new File(syncedFolder.getLocalPath()).getName()); + } + /** * creates a SyncedFolderDisplayItem merging a {@link SyncedFolder} and a {@link MediaFolder} object instance. * @@ -273,7 +306,6 @@ private Map createSyncedFoldersMap(List sync } return result; } - /** * show/hide recycler view list or the empty message / progress info. * @@ -289,7 +321,7 @@ private void setListShown(boolean shown) { @Override public boolean onOptionsItemSelected(MenuItem item) { - boolean result; + boolean result = true; switch (item.getItemId()) { case android.R.id.home: { if (isDrawerOpen()) { @@ -297,6 +329,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } else { openDrawer(); } + break; } default: @@ -323,9 +356,13 @@ public void showFiles(boolean onDeviceOnly) { @Override public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) { if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) { - mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderDisplayItem.getId(), syncedFolderDisplayItem.isEnabled()); + mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderDisplayItem.getId(), + syncedFolderDisplayItem.isEnabled()); } else { - mSyncedFolderProvider.storeFolderSync(syncedFolderDisplayItem); + long storedId = mSyncedFolderProvider.storeFolderSync(syncedFolderDisplayItem); + if (storedId != -1) { + syncedFolderDisplayItem.setId(storedId); + } } } @@ -362,7 +399,11 @@ public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { if (syncedFolder.getId() == UNPERSISTED_ID) { // newly set up folder sync config - mSyncedFolderProvider.storeFolderSync(item); + long storedId = mSyncedFolderProvider.storeFolderSync(item); + if (storedId != -1) { + item.setId(storedId); + } + } else { // existing synced folder setup to be updated mSyncedFolderProvider.updateSyncFolder(item); diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java index bda0ca12587c..8802c72c720d 100644 --- a/src/com/owncloud/android/ui/activity/Preferences.java +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -368,8 +368,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category"); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - // Instant upload via preferences on pre Android Nougat + 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) { diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 47b1dbfe95c2..8c21374390d2 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -77,7 +77,11 @@ public int getSectionCount() { @Override public int getItemCount(int section) { - return mSyncFolderItems.get(section).getFilePaths().size(); + if (mSyncFolderItems.get(section).getFilePaths() != null) { + return mSyncFolderItems.get(section).getFilePaths().size(); + } else { + return 1; + } } @Override @@ -108,37 +112,44 @@ public void onClick(View v) { @Override public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { - File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition)); + if (mSyncFolderItems.get(section).getFilePaths() != null) { + File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition)); - ThumbnailsCacheManager.MediaThumbnailGenerationTask task = - new ThumbnailsCacheManager.MediaThumbnailGenerationTask(holder.image); + ThumbnailsCacheManager.MediaThumbnailGenerationTask task = + new ThumbnailsCacheManager.MediaThumbnailGenerationTask(holder.image); - ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable( - mContext.getResources(), - ThumbnailsCacheManager.mDefaultImg, - task - ); - holder.image.setImageDrawable(asyncDrawable); + ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable = + new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable( + mContext.getResources(), + ThumbnailsCacheManager.mDefaultImg, + task + ); + holder.image.setImageDrawable(asyncDrawable); - task.execute(file); + task.execute(file); - // set proper tag - holder.image.setTag(file.hashCode()); + // set proper tag + holder.image.setTag(file.hashCode()); - holder.itemView.setTag(relativePosition % mGridWidth); + holder.itemView.setTag(relativePosition % mGridWidth); + + if (mSyncFolderItems.get(section).getNumberOfFiles() > mGridTotal && relativePosition >= mGridTotal - 1) { + holder.counterValue.setText(Long.toString(mSyncFolderItems.get(section).getNumberOfFiles() - mGridTotal)); + holder.counterBar.setVisibility(View.VISIBLE); + holder.thumbnailDarkener.setVisibility(View.VISIBLE); + } else { + holder.counterBar.setVisibility(View.GONE); + holder.thumbnailDarkener.setVisibility(View.GONE); + } - if (mSyncFolderItems.get(section).getNumberOfFiles() > mGridTotal && relativePosition >= mGridTotal - 1) { - holder.counterValue.setText(Long.toString(mSyncFolderItems.get(section).getNumberOfFiles() - mGridTotal)); + //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); - } else { - 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); } @Override diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index 1eec561edd01..fafb31f9d314 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -39,6 +39,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -47,6 +48,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.TimeZone; import java.util.Vector; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -62,6 +64,7 @@ public class FileStorageUtils { public static final Integer SORT_NAME = 0; public static final Integer SORT_DATE = 1; public static final Integer SORT_SIZE = 2; + public static final String PATTERN_YYYY_MM = "yyyy/MM/"; public static Integer mSortOrder = SORT_NAME; public static Boolean mSortAscending = true; @@ -122,42 +125,70 @@ public static String getLogPath() { * string is returned * * @param date: date in microseconds since 1st January 1970 - * @return + * @return string: yyyy/mm/ */ - private static String getSubpathFromDate(long date) { + private static String getSubpathFromDate(long date, Locale currentLocale) { if (date == 0) { return ""; } - try { - SimpleDateFormat formatter = new SimpleDateFormat( - "yyyy" + OCFile.PATH_SEPARATOR + "MM" + OCFile.PATH_SEPARATOR, Locale.ENGLISH); - return formatter.format(new Date(date)); - } - catch(RuntimeException ex) { - Log_OC.w(TAG, "could not extract date from timestamp"); + + Date d = new Date(date); + + DateFormat df = new SimpleDateFormat(PATTERN_YYYY_MM, currentLocale); + df.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID())); + + return df.format(d); + } + + private static String getSubpathFromDate(long date) { + if (date == 0) { return ""; } + + Date d = new Date(date); + + DateFormat df = new SimpleDateFormat(PATTERN_YYYY_MM); + + return df.format(d); } /** - * Returns the InstantUploadFilePath on the owncloud instance + * Returns the InstantUploadFilePath on the nextcloud instance * - * @param fileName + * @param fileName complete file name * @param dateTaken: Time in milliseconds since 1970 when the picture was taken. - * @return + * @return instantUpload path, eg. /Camera/2017/01/fileName */ - public static String getInstantUploadFilePath(String remotePath, String fileName, long dateTaken, + public static String getInstantUploadFilePath(Locale current, + String remotePath, + String fileName, + long dateTaken, + Boolean subfolderByDate) { + String subPath = ""; + if (subfolderByDate) { + subPath = getSubpathFromDate(dateTaken, current); + } + + return remotePath + OCFile.PATH_SEPARATOR + subPath + (fileName == null ? "" : fileName); + } + + public static String getInstantUploadFilePath(String remotePath, + String fileName, + long dateTaken, Boolean subfolderByDate) { String subPath = ""; if (subfolderByDate) { - subPath = getSubpathFromDate(dateTaken); + subPath = getSubpathFromDate(dateTaken); } + return remotePath + OCFile.PATH_SEPARATOR + subPath + (fileName == null ? "" : fileName); } + /** - * Gets the composed path when video is or must be stored - * @param context + * Gets the composed path when video is or must be stored. + * + * @param context app context * @param fileName: video file name * @return String: video file path composed */ @@ -169,8 +200,7 @@ public static String getInstantVideoUploadFilePath(Context context, String fileN if (com.owncloud.android.db.PreferenceManager.instantVideoUploadPathUseSubfolders(context)) { subPath = getSubpathFromDate(dateTaken); } - return uploadVideoPath + OCFile.PATH_SEPARATOR + subPath - + (fileName == null ? "" : fileName); + return uploadVideoPath + subPath + (fileName == null ? "" : fileName); } public static String getParentPath(String remotePath) { @@ -241,7 +271,9 @@ public static Vector sortOcFolder(Vector files){ } /** - * Sorts all filenames, regarding last user decision + * Sorts all filenames, regarding last user decision. + * + * @param files of files to sort */ public static File[] sortLocalFolder(File[] files){ switch (mSortOrder){ @@ -260,8 +292,9 @@ public static File[] sortLocalFolder(File[] files){ } /** - * Sorts list by Date - * @param files + * Sorts list by Date. + * + * @param files list of files to sort */ public static Vector sortOCFilesByDate(Vector files){ final int multiplier = mSortAscending ? 1 : -1; @@ -278,8 +311,9 @@ public int compare(OCFile o1, OCFile o2) { } /** - * Sorts list by Date - * @param filesArray + * Sorts list by Date. + * + * @param filesArray list of files to sort */ public static File[] sortLocalFilesByDate(File[] filesArray){ final int multiplier = mSortAscending ? 1 : -1; @@ -299,7 +333,9 @@ public int compare(File o1, File o2) { } /** - * Sorts list by Size + * Sorts list by Size. + * + * @param files list of files to sort */ public static Vector sortOCFilesBySize(Vector files){ final int multiplier = mSortAscending ? 1 : -1; @@ -326,7 +362,9 @@ public int compare(OCFile o1, OCFile o2) { } /** - * Sorts list by Size + * Sorts list by Size. + * + * @param filesArray list of files to sort */ public static File[] sortLocalFilesBySize(File[] filesArray) { final int multiplier = mSortAscending ? 1 : -1; @@ -356,8 +394,9 @@ public int compare(File o1, File o2) { } /** - * Sorts list by Name - * @param files files to sort + * Sorts list by Name. + * + * @param files files to sort */ @SuppressFBWarnings(value = "Bx") public static Vector sortOCFilesByName(Vector files){ @@ -380,8 +419,9 @@ public int compare(OCFile o1, OCFile o2) { } /** - * Sorts list by Name - * @param filesArray files to sort + * Sorts list by Name. + * + * @param filesArray files to sort */ public static File[] sortLocalFilesByName(File[] filesArray) { final int multiplier = mSortAscending ? 1 : -1; @@ -407,8 +447,9 @@ public int compare(File o1, File o2) { } /** - * Sorts list by Favourites - * @param files files to sort + * Sorts list by Favourites. + * + * @param files files to sort */ public static Vector sortOCFilesByFavourite(Vector files){ Collections.sort(files, new Comparator() { diff --git a/src/com/owncloud/android/utils/RecursiveFileObserver.java b/src/com/owncloud/android/utils/RecursiveFileObserver.java deleted file mode 100644 index b482882c43f6..000000000000 --- a/src/com/owncloud/android/utils/RecursiveFileObserver.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * ownCloud Android client application - * - * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2015 ownCloud Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package com.owncloud.android.utils; - -import android.os.FileObserver; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - -public class RecursiveFileObserver extends FileObserver { - - private final List mObservers = new ArrayList<>(); - private boolean watching = false; - private String mPath; - private int mMask; - - public RecursiveFileObserver(String path) { - this(path, ALL_EVENTS); - } - - public RecursiveFileObserver(String path, int mask) { - super(path, mask); - mPath = path; - mMask = mask; - } - - @Override - public void startWatching() { - if (watching) { - return; - } - watching = true; - final Stack stack = new Stack(); - stack.push(mPath); - - while (!stack.empty()) { - String parent = stack.pop(); - mObservers.add(new SingleFileObserver(parent, mMask)); - File path = new File(parent); - File[] files = path.listFiles(); - if (files == null) { - continue; - } - for (final File file : files) { - if (file.isDirectory() && !file.getName().equals(".") - && !file.getName().equals("..")) { - stack.push(file.getPath()); - } - } - } - for (int i = 0; i < mObservers.size(); i++) { - mObservers.get(i).startWatching(); - } - } - - @Override - public void stopWatching() { - if (!watching) { - return; - } - - for (int i = 0; i < mObservers.size(); ++i) { - mObservers.get(i).stopWatching(); - } - mObservers.clear(); - watching = false; - } - - @Override - public void onEvent(int event, String path) { - - } - - private class SingleFileObserver extends FileObserver { - private String mPath; - - SingleFileObserver(String path, int mask) { - super(path, mask); - mPath = path; - } - - @Override - public void onEvent(int event, String path) { - String newPath = mPath + "/" + path; - RecursiveFileObserver.this.onEvent(event, newPath); - } - - } -}