diff --git a/src/main/java/com/owncloud/android/MainApp.java b/src/main/java/com/owncloud/android/MainApp.java index 9dcbc15dbfae..0e44857294eb 100644 --- a/src/main/java/com/owncloud/android/MainApp.java +++ b/src/main/java/com/owncloud/android/MainApp.java @@ -44,6 +44,7 @@ import android.view.WindowManager; import com.evernote.android.job.JobManager; +import com.evernote.android.job.JobRequest; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.authentication.PassCodeManager; import com.owncloud.android.datamodel.ArbitraryDataProvider; @@ -56,6 +57,7 @@ import com.owncloud.android.datastorage.DataStorageProvider; import com.owncloud.android.datastorage.StoragePoint; import com.owncloud.android.db.PreferenceManager; +import com.owncloud.android.jobs.MediaFoldersDetectionJob; import com.owncloud.android.jobs.NCJobCreator; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy; @@ -77,6 +79,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -164,6 +167,20 @@ public void onCreate() { initContactsBackup(); notificationChannels(); + + new JobRequest.Builder(MediaFoldersDetectionJob.TAG) + .setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5)) + .setUpdateCurrent(true) + .build() + .schedule(); + + new JobRequest.Builder(MediaFoldersDetectionJob.TAG) + .startNow() + .setUpdateCurrent(false) + .build() + .schedule(); + + // register global protection with pass code registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @@ -336,6 +353,10 @@ public static void notificationChannels() { createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_PUSH, R.string.notification_channel_push_name, R.string .notification_channel_push_description, context, NotificationManager.IMPORTANCE_DEFAULT); + + createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL, R.string + .notification_channel_general_name, R.string.notification_channel_general_description, + context, NotificationManager.IMPORTANCE_DEFAULT); } else { Log_OC.e(TAG, "Notification manager is null"); } @@ -546,8 +567,8 @@ private static void splitOutAutoUploadEntries() { SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver); - final List imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null); - final List videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null); + final List imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true); + final List videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true); ArrayList idsToDelete = new ArrayList<>(); List syncedFolders = syncedFolderProvider.getSyncedFolders(); diff --git a/src/main/java/com/owncloud/android/datamodel/MediaFoldersModel.java b/src/main/java/com/owncloud/android/datamodel/MediaFoldersModel.java new file mode 100644 index 000000000000..dbc9ee0a4d3a --- /dev/null +++ b/src/main/java/com/owncloud/android/datamodel/MediaFoldersModel.java @@ -0,0 +1,56 @@ +/* + * Nextcloud Android client application + * + * @author Mario Danic + * Copyright (C) 2018 Mario Danic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ + +package com.owncloud.android.datamodel; + +import java.util.List; + +public class MediaFoldersModel { + private List imageMediaFolders; + private List videoMediaFolders; + + /** + * default constructor. + */ + public MediaFoldersModel() { + // keep default constructor for GSON + } + + public MediaFoldersModel(List imageMediaFolders, List videoMediaFolders) { + this.imageMediaFolders = imageMediaFolders; + this.videoMediaFolders = videoMediaFolders; + } + + public List getImageMediaFolders() { + return imageMediaFolders; + } + + public void setImageMediaFolders(List imageMediaFolders) { + this.imageMediaFolders = imageMediaFolders; + } + + public List getVideoMediaFolders() { + return videoMediaFolders; + } + + public void setVideoMediaFolders(List videoMediaFolders) { + this.videoMediaFolders = videoMediaFolders; + } +} diff --git a/src/main/java/com/owncloud/android/datamodel/MediaProvider.java b/src/main/java/com/owncloud/android/datamodel/MediaProvider.java index 12fe8b3e095a..72997bcac178 100644 --- a/src/main/java/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/main/java/com/owncloud/android/datamodel/MediaProvider.java @@ -66,14 +66,14 @@ public class MediaProvider { * @return list with media folders */ public static List getImageFolders(ContentResolver contentResolver, int itemLimit, - @Nullable final Activity activity) { + @Nullable final Activity activity, boolean getWithoutActivity) { // check permissions checkPermissions(activity); // query media/image folders Cursor cursorFolders = null; - if (activity != null && PermissionUtil.checkSelfPermission(activity.getApplicationContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + if ((activity != null && PermissionUtil.checkSelfPermission(activity.getApplicationContext(), + Manifest.permission.WRITE_EXTERNAL_STORAGE)) || getWithoutActivity) { cursorFolders = contentResolver.query(IMAGES_MEDIA_URI, IMAGES_FOLDER_PROJECTION, null, null, IMAGES_FOLDER_SORT_ORDER); } @@ -171,14 +171,14 @@ private static void checkPermissions(@Nullable Activity activity) { } public static List getVideoFolders(ContentResolver contentResolver, int itemLimit, - @Nullable final Activity activity) { + @Nullable final Activity activity, boolean getWithoutActivity) { // check permissions checkPermissions(activity); // query media/image folders Cursor cursorFolders = null; - if (activity != null && PermissionUtil.checkSelfPermission(activity.getApplicationContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + if ((activity != null && PermissionUtil.checkSelfPermission(activity.getApplicationContext(), + Manifest.permission.WRITE_EXTERNAL_STORAGE)) || getWithoutActivity) { cursorFolders = contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, VIDEOS_FOLDER_PROJECTION, null, null, null); } diff --git a/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java index 1ead7c225e3d..bcc97a7572fe 100644 --- a/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -78,6 +78,24 @@ public long storeSyncedFolder(SyncedFolder syncedFolder) { } } + public int countEnabledSyncedFolders() { + int count = 0; + Cursor cursor = mContentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED + " = ?", + new String[]{"1"}, + null + ); + + if (cursor != null) { + count = cursor.getCount(); + cursor.close(); + } + + return count; + } + /** * get all synced folder entries. * @@ -160,6 +178,37 @@ public int updateSyncedFolderEnabled(long id, Boolean enabled) { return result; } + public SyncedFolder findByLocalPathAndAccount(String localPath, Account account) { + + SyncedFolder result = null; + Cursor cursor = mContentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + " == \"" + localPath + "\"" + " AND " + + ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " == " + account.name, + null, + null + ); + + if (cursor != null && cursor.getCount() == 1) { + result = createSyncedFolderFromCursor(cursor); + } else { + if (cursor == null) { + Log_OC.e(TAG, "Sync folder db cursor for local path=" + localPath + " in NULL."); + } else { + Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath + + " available in sync folder db. Expected 1. Failed to update sync folder db."); + } + } + + if (cursor != null) { + cursor.close(); + } + + return result; + + } + /** * find a synced folder by local path. * @@ -171,7 +220,7 @@ public SyncedFolder findByLocalPath(String localPath) { Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, - ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + "== \"" + localPath + "\"", + ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + " == \"" + localPath + "\"", null, null ); diff --git a/src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java b/src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java new file mode 100644 index 000000000000..a097bcfc5ce6 --- /dev/null +++ b/src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java @@ -0,0 +1,173 @@ +/* + * Nextcloud Android client application + * + * @author Mario Danic + * @author Andy Scherzinger + * Copyright (C) 2018 Mario Danic + * Copyright (C) 2018 Andy Scherzinger + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this program. If not, see . + */ +package com.owncloud.android.jobs; + + +import android.accounts.Account; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.media.RingtoneManager; +import android.support.annotation.NonNull; +import android.support.v4.app.NotificationCompat; +import android.text.TextUtils; + +import com.evernote.android.job.Job; +import com.google.gson.Gson; +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.MediaFoldersModel; +import com.owncloud.android.datamodel.MediaProvider; +import com.owncloud.android.datamodel.SyncedFolderProvider; +import com.owncloud.android.ui.activity.ManageAccountsActivity; +import com.owncloud.android.ui.activity.SyncedFoldersActivity; +import com.owncloud.android.ui.notifications.NotificationUtils; +import com.owncloud.android.utils.ThemeUtils; + +import java.util.ArrayList; +import java.util.List; + +public class MediaFoldersDetectionJob extends Job { + public static final String TAG = "MediaFoldersDetectionJob"; + + public static final String KEY_MEDIA_FOLDER_PATH = "KEY_MEDIA_FOLDER_PATH"; + public static final String KEY_MEDIA_FOLDER_TYPE = "KEY_MEDIA_FOLDER_TYPE"; + + private static final String ACCOUNT_NAME_GLOBAL = "global"; + private static final String KEY_MEDIA_FOLDERS = "media_folders"; + + @NonNull + @Override + protected Result onRunJob(@NonNull Params params) { + Context context = getContext(); + ContentResolver contentResolver = context.getContentResolver(); + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver); + SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver); + Gson gson = new Gson(); + String arbitraryDataString; + MediaFoldersModel mediaFoldersModel; + + List imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, + null, true); + List videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, + true); + + List imageMediaFolderPaths = new ArrayList<>(); + List videoMediaFolderPaths = new ArrayList<>(); + + for (MediaFolder imageMediaFolder: imageMediaFolders) { + imageMediaFolderPaths.add(imageMediaFolder.absolutePath); + } + + for (MediaFolder videoMediaFolder: videoMediaFolders) { + imageMediaFolderPaths.add(videoMediaFolder.absolutePath); + } + + arbitraryDataString = arbitraryDataProvider.getValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS); + if (!TextUtils.isEmpty(arbitraryDataString)) { + mediaFoldersModel = gson.fromJson(arbitraryDataString, MediaFoldersModel.class); + + // Store updated values + arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS, gson.toJson(new + MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths))); + + imageMediaFolderPaths.removeAll(mediaFoldersModel.getImageMediaFolders()); + videoMediaFolderPaths.removeAll(mediaFoldersModel.getVideoMediaFolders()); + + if (imageMediaFolderPaths.size() > 0 || videoMediaFolderPaths.size() > 0) { + Account[] accounts = AccountUtils.getAccounts(getContext()); + List accountList = new ArrayList<>(); + for (Account account : accounts) { + if (!arbitraryDataProvider.getBooleanValue(account, ManageAccountsActivity.PENDING_FOR_REMOVAL)) { + accountList.add(account); + } + } + + for (Account account : accountList) { + for (String imageMediaFolder : imageMediaFolderPaths) { + if (syncedFolderProvider.findByLocalPathAndAccount(imageMediaFolder, + account) == null) { + sendNotification(String.format(context.getString(R.string.new_media_folder_detected), + context.getString(R.string.new_media_folder_photos)), + imageMediaFolder.substring(imageMediaFolder.lastIndexOf('/') + 1, + imageMediaFolder.length()), + account, imageMediaFolder, 1); + } + } + + for (String videoMediaFolder : videoMediaFolderPaths) { + if (syncedFolderProvider.findByLocalPathAndAccount(videoMediaFolder, + account) == null) { + sendNotification(String.format(context.getString(R.string.new_media_folder_detected), + context.getString(R.string.new_media_folder_videos)), + videoMediaFolder.substring(videoMediaFolder.lastIndexOf('/') + 1, + videoMediaFolder.length()), + account, videoMediaFolder, 2); + } + } + } + } + + } else { + mediaFoldersModel = new MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths); + arbitraryDataProvider.storeOrUpdateKeyValue("global", "media_folders", gson.toJson(mediaFoldersModel)); + } + + return Result.SUCCESS; + } + + private void sendNotification(String contentTitle, String subtitle, Account account, + String path, int type) { + Context context = getContext(); + Intent intent = new Intent(getContext(), SyncedFoldersActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(NotificationJob.KEY_NOTIFICATION_ACCOUNT, account.name); + intent.putExtra(KEY_MEDIA_FOLDER_PATH, path); + intent.putExtra(KEY_MEDIA_FOLDER_TYPE, type); + intent.putExtra(SyncedFoldersActivity.EXTRA_SHOW_SIDEBAR, true); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT); + + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( + context, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL) + .setSmallIcon(R.drawable.notification_icon) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.notification_icon)) + .setColor(ThemeUtils.primaryColor(getContext())) + .setSubText(account.name) + .setContentTitle(contentTitle) + .setContentText(subtitle) + .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) + .setAutoCancel(true) + .setContentIntent(pendingIntent); + + NotificationManager notificationManager = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + + if (notificationManager != null) { + notificationManager.notify(0, notificationBuilder.build()); + } + } +} diff --git a/src/main/java/com/owncloud/android/jobs/NCJobCreator.java b/src/main/java/com/owncloud/android/jobs/NCJobCreator.java index 9aeaec306a02..a63b47485f42 100644 --- a/src/main/java/com/owncloud/android/jobs/NCJobCreator.java +++ b/src/main/java/com/owncloud/android/jobs/NCJobCreator.java @@ -43,6 +43,8 @@ public Job create(String tag) { return new OfflineSyncJob(); case NotificationJob.TAG: return new NotificationJob(); + case MediaFoldersDetectionJob.TAG: + return new MediaFoldersDetectionJob(); default: return null; } diff --git a/src/main/java/com/owncloud/android/jobs/NContentObserverJob.java b/src/main/java/com/owncloud/android/jobs/NContentObserverJob.java index 2ecc4dc7839c..978d4aca28ea 100644 --- a/src/main/java/com/owncloud/android/jobs/NContentObserverJob.java +++ b/src/main/java/com/owncloud/android/jobs/NContentObserverJob.java @@ -1,4 +1,4 @@ -/** +/* * Nextcloud Android client application * * @author Mario Danic @@ -28,11 +28,13 @@ import com.evernote.android.job.JobRequest; import com.evernote.android.job.util.support.PersistableBundleCompat; +import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.utils.FilesSyncHelper; import com.owncloud.android.utils.PowerUtils; /* Job that triggers new FilesSyncJob in case new photo or video were detected + and starts a job to find new media folders */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public class NContentObserverJob extends JobService { @@ -42,26 +44,39 @@ 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 - && !PowerUtils.isPowerSaveMode(getApplicationContext())) { + && params.getTriggeredContentUris().length > 0) { - PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat(); - persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true); + checkAndStartFileSyncJob(); - new JobRequest.Builder(FilesSyncJob.TAG) + new JobRequest.Builder(MediaFoldersDetectionJob.TAG) .startNow() - .setExtras(persistableBundleCompat) .setUpdateCurrent(false) .build() .schedule(); + } - FilesSyncHelper.scheduleNJobs(true, getApplicationContext()); + FilesSyncHelper.scheduleJobOnN(); } return true; } + private void checkAndStartFileSyncJob() { + if (!PowerUtils.isPowerSaveMode(getApplicationContext()) && + new SyncedFolderProvider(getContentResolver()).countEnabledSyncedFolders() > 0) { + PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat(); + persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true); + + new JobRequest.Builder(FilesSyncJob.TAG) + .startNow() + .setExtras(persistableBundleCompat) + .setUpdateCurrent(false) + .build() + .schedule(); + } + } + @Override public boolean onStopJob(JobParameters params) { return false; diff --git a/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java b/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java index 9c056102c674..2267636230d8 100644 --- a/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java @@ -37,6 +37,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; import android.util.Log; import android.view.MenuItem; import android.view.View; @@ -56,6 +57,8 @@ import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.jobs.MediaFoldersDetectionJob; +import com.owncloud.android.jobs.NotificationJob; import com.owncloud.android.ui.adapter.SyncedFolderAdapter; import com.owncloud.android.ui.decoration.MediaGridItemDecoration; import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment; @@ -85,10 +88,9 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderAdapter.ClickListener, SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener { - private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG"; - public static final String[] PRIORITIZED_FOLDERS = new String[] { "Camera", "Screenshots" }; + public static final String[] PRIORITIZED_FOLDERS = new String[]{"Camera", "Screenshots"}; public static final String EXTRA_SHOW_SIDEBAR = "SHOW_SIDEBAR"; - + private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG"; private static final String TAG = SyncedFoldersActivity.class.getSimpleName(); private RecyclerView mRecyclerView; @@ -100,6 +102,9 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA private boolean showSidebar = true; private RelativeLayout mCustomFolderRelativeLayout; + private String path; + private int type; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -110,6 +115,21 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.synced_folders_layout); + String account; + Account currentAccount; + if (getIntent() != null && getIntent().getExtras() != null) { + account = getIntent().getExtras().getString(NotificationJob.KEY_NOTIFICATION_ACCOUNT); + currentAccount = AccountUtils.getCurrentOwnCloudAccount(getApplicationContext()); + + if (account != null && currentAccount != null && !account.equalsIgnoreCase(currentAccount.name)) { + AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), account); + setAccount(AccountUtils.getCurrentOwnCloudAccount(this)); + } + + path = getIntent().getStringExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_PATH); + type = getIntent().getIntExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_TYPE, -1); + } + // setup toolbar setupToolbar(); CollapsingToolbarLayout mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); @@ -199,9 +219,9 @@ private void load(final int perFolderMediaItemLimit, boolean force) { } setListShown(false); final List mediaFolders = MediaProvider.getImageFolders(getContentResolver(), - perFolderMediaItemLimit, SyncedFoldersActivity.this); + perFolderMediaItemLimit, this, false); mediaFolders.addAll(MediaProvider.getVideoFolders(getContentResolver(), perFolderMediaItemLimit, - SyncedFoldersActivity.this)); + this, false)); List syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders(); List currentAccountSyncedFoldersList = new ArrayList<>(); @@ -218,41 +238,13 @@ private void load(final int perFolderMediaItemLimit, boolean force) { mAdapter.setSyncFolderItems(syncFolderItems); mAdapter.notifyDataSetChanged(); setListShown(true); - } - - /** - * merges two lists of {@link SyncedFolder} and {@link MediaFolder} items into one of SyncedFolderItems. - * - * @param syncedFolders the synced folders - * @param mediaFolders the media folders - * @return the merged list of SyncedFolderItems - */ - @NonNull - private List mergeFolderData(List syncedFolders, - @NonNull List mediaFolders) { - Map syncedFoldersMap = createSyncedFoldersMap(syncedFolders); - List result = new ArrayList<>(); - - for (MediaFolder mediaFolder : mediaFolders) { - 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)); + if (!TextUtils.isEmpty(path)) { + int section = mAdapter.getSectionByLocalPathAndType(path, type); + if (section >= 0) { + onSyncFolderSettingsClick(section, mAdapter.get(section)); } } - - for (SyncedFolder syncedFolder : syncedFoldersMap.values()) { - result.add(createSyncedFolderWithoutMediaFolder(syncedFolder)); - } - - return result; } /** @@ -296,13 +288,48 @@ public int compare(SyncedFolderDisplayItem f1, SyncedFolderDisplayItem f2) { } } return f1.getFolderName().toLowerCase(Locale.getDefault()).compareTo( - f2.getFolderName().toLowerCase(Locale.getDefault())); + f2.getFolderName().toLowerCase(Locale.getDefault())); } }); return syncFolderItemList; } + /** + * merges two lists of {@link SyncedFolder} and {@link MediaFolder} items into one of SyncedFolderItems. + * + * @param syncedFolders the synced folders + * @param mediaFolders the media folders + * @return the merged list of SyncedFolderItems + */ + @NonNull + private List mergeFolderData(List syncedFolders, + @NonNull List mediaFolders) { + Map syncedFoldersMap = createSyncedFoldersMap(syncedFolders); + List result = new ArrayList<>(); + + for (MediaFolder mediaFolder : mediaFolders) { + 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()) { + result.add(createSyncedFolderWithoutMediaFolder(syncedFolder)); + } + + return result; + } + @NonNull private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) { @@ -420,7 +447,7 @@ private Map createSyncedFoldersMap(List sync Map result = new HashMap<>(); if (syncFolders != null) { for (SyncedFolder syncFolder : syncFolders) { - result.put(syncFolder.getLocalPath()+"-"+syncFolder.getType(), syncFolder); + result.put(syncFolder.getLocalPath() + "-" + syncFolder.getType(), syncFolder); } } return result; @@ -501,8 +528,6 @@ public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedF String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolderDisplayItem.getId(); arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey); } - FilesSyncHelper.scheduleNJobs(false, getApplicationContext()); - } @Override @@ -522,12 +547,12 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) { OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER); mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath()); - } if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_LOCAL_FOLDER + } + 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 { + } else { super.onActivityResult(requestCode, resultCode, data); } } @@ -554,7 +579,6 @@ public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + newCustomFolder.getId(); arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey); } - FilesSyncHelper.scheduleNJobs(false, getApplicationContext()); } mAdapter.addSyncFolderItem(newCustomFolder); } else { @@ -574,7 +598,6 @@ public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId(); arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey); } - FilesSyncHelper.scheduleNJobs(false, getApplicationContext()); } } else { // existing synced folder setup to be updated @@ -585,7 +608,6 @@ public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId(); arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey); } - FilesSyncHelper.scheduleNJobs(false, getApplicationContext()); } mAdapter.setSyncFolderItem(syncedFolder.getSection(), item); diff --git a/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java index f5bd355a86f1..e7af2e555a2e 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java @@ -108,6 +108,24 @@ public SyncedFolderDisplayItem get(int section) { return mSyncFolderItems.get(section); } + /** + * returns the section of a synced folder for the given local path and type. + * + * @param localPath the local path of the synced folder + * @param type the of the synced folder + * @return the section index of the looked up synced folder, -1 if not present + */ + public int getSectionByLocalPathAndType(String localPath, int type) { + for (int i = 0; i < mSyncFolderItems.size(); i++) { + if (mSyncFolderItems.get(i).getLocalPath().equalsIgnoreCase(localPath) && + mSyncFolderItems.get(i).getType().getId().equals(type)) { + return i; + } + } + + return -1; + } + @Override public void onBindHeaderViewHolder(final MainViewHolder holder, final int section, boolean expanded) { holder.mainHeaderContainer.setVisibility(View.VISIBLE); diff --git a/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java b/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java index fa9917eb8d70..3d68b5077752 100644 --- a/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java +++ b/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java @@ -34,6 +34,7 @@ public class NotificationUtils { + public static final String NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL"; public static final String NOTIFICATION_CHANNEL_DOWNLOAD = "NOTIFICATION_CHANNEL_DOWNLOAD"; public static final String NOTIFICATION_CHANNEL_UPLOAD = "NOTIFICATION_CHANNEL_UPLOAD"; public static final String NOTIFICATION_CHANNEL_MEDIA = "NOTIFICATION_CHANNEL_MEDIA"; diff --git a/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java b/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java index 94f8a4420a4c..07146381530d 100644 --- a/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java +++ b/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java @@ -1,4 +1,4 @@ -/** +/* * Nextcloud Android client application * * @author Mario Danic @@ -60,7 +60,6 @@ import java.io.File; import java.io.IOException; -import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -240,52 +239,6 @@ public static void restartJobsIfNeeded() { }).start(); } - @RequiresApi(api = Build.VERSION_CODES.N) - public static boolean isContentObserverJobScheduled() { - JobScheduler js = MainApp.getAppContext().getSystemService(JobScheduler.class); - List jobs = js.getAllPendingJobs(); - - if (jobs == null || jobs.size() == 0) { - return false; - } - - for (int i = 0; i < jobs.size(); i++) { - if (jobs.get(i).getId() == ContentSyncJobId) { - return true; - } - } - - return false; - } - - public static void scheduleNJobs(boolean force, Context context) { - SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(context.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(Context context) { // always run this because it also allows us to perform retries of manual uploads new JobRequest.Builder(FilesSyncJob.TAG) @@ -294,8 +247,8 @@ public static void scheduleFilesSyncIfNeeded(Context context) { .build() .schedule(); - if (context != null) { - scheduleNJobs(false, context); + if (context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + scheduleJobOnN(); } } @@ -310,21 +263,11 @@ public static void scheduleOfflineSyncIfNeeded() { } } - - @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) { + public static void scheduleJobOnN() { JobScheduler jobScheduler = MainApp.getAppContext().getSystemService(JobScheduler.class); - if ((hasImageFolders || hasVideoFolders) && (!isContentObserverJobScheduled() || force)) { + if (jobScheduler != null) { JobInfo.Builder builder = new JobInfo.Builder(ContentSyncJobId, new ComponentName(MainApp.getAppContext(), NContentObserverJob.class.getName())); builder.addTriggerContentUri(new JobInfo.TriggerContentUri(android.provider.MediaStore. diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index ac6aa97033fa..0fe18c90bd96 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -652,7 +652,6 @@ Backup scheduled and will start shortly Import scheduled and will start shortly - Log out No app found to set a picture with Privacy @@ -781,6 +780,7 @@ Push notifications Show push notifications sent by the server: Mentions in comments, reception of new remote shares, announcements posted by an admin etc. Send button icon + Auth end point address Access end point address * @@ -804,6 +804,12 @@ Error commenting file Successfully restored file version. Error restoring file version! + General notifications + Show notifications for new media folders and similar + New %1$s media folder detected. + photo + video + The server has reached end of life, please upgrade! Dismiss No app available to send mails!