MediaFoldersDetectionJob.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Mario Danic
  5. * @author Andy Scherzinger
  6. * @author Chris Narkiewicz
  7. * Copyright (C) 2018 Mario Danic
  8. * Copyright (C) 2018 Andy Scherzinger
  9. * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
  10. *
  11. * This program is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  13. * License as published by the Free Software Foundation; either
  14. * version 3 of the License, or any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public
  22. * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. */
  24. package com.owncloud.android.jobs;
  25. import android.app.Activity;
  26. import android.app.NotificationManager;
  27. import android.app.PendingIntent;
  28. import android.content.BroadcastReceiver;
  29. import android.content.ContentResolver;
  30. import android.content.Context;
  31. import android.content.Intent;
  32. import android.graphics.BitmapFactory;
  33. import android.media.RingtoneManager;
  34. import android.text.TextUtils;
  35. import com.evernote.android.job.Job;
  36. import com.google.gson.Gson;
  37. import com.nextcloud.client.account.User;
  38. import com.nextcloud.client.account.UserAccountManager;
  39. import com.nextcloud.client.core.Clock;
  40. import com.nextcloud.client.preferences.AppPreferences;
  41. import com.nextcloud.client.preferences.AppPreferencesImpl;
  42. import com.owncloud.android.R;
  43. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  44. import com.owncloud.android.datamodel.MediaFolder;
  45. import com.owncloud.android.datamodel.MediaFoldersModel;
  46. import com.owncloud.android.datamodel.MediaProvider;
  47. import com.owncloud.android.datamodel.SyncedFolder;
  48. import com.owncloud.android.datamodel.SyncedFolderProvider;
  49. import com.owncloud.android.lib.common.utils.Log_OC;
  50. import com.owncloud.android.ui.activity.ManageAccountsActivity;
  51. import com.owncloud.android.ui.activity.SyncedFoldersActivity;
  52. import com.owncloud.android.ui.notifications.NotificationUtils;
  53. import com.owncloud.android.utils.ThemeUtils;
  54. import java.util.ArrayList;
  55. import java.util.List;
  56. import java.util.Random;
  57. import androidx.annotation.NonNull;
  58. import androidx.core.app.NotificationCompat;
  59. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  60. @SuppressFBWarnings(value = "PREDICTABLE_RANDOM", justification = "Only used for notification id.")
  61. public class MediaFoldersDetectionJob extends Job {
  62. public static final String TAG = "MediaFoldersDetectionJob";
  63. public static final String KEY_MEDIA_FOLDER_PATH = "KEY_MEDIA_FOLDER_PATH";
  64. public static final String KEY_MEDIA_FOLDER_TYPE = "KEY_MEDIA_FOLDER_TYPE";
  65. private static final String ACCOUNT_NAME_GLOBAL = "global";
  66. private static final String KEY_MEDIA_FOLDERS = "media_folders";
  67. public static final String NOTIFICATION_ID = "NOTIFICATION_ID";
  68. private static final String DISABLE_DETECTION_CLICK = "DISABLE_DETECTION_CLICK";
  69. private final UserAccountManager userAccountManager;
  70. private final Clock clock;
  71. private final Random randomId = new Random();
  72. MediaFoldersDetectionJob(UserAccountManager accountManager, Clock clock) {
  73. this.userAccountManager = accountManager;
  74. this.clock = clock;
  75. }
  76. @NonNull
  77. @Override
  78. protected Result onRunJob(@NonNull Params params) {
  79. Context context = getContext();
  80. ContentResolver contentResolver = context.getContentResolver();
  81. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
  82. SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver,
  83. AppPreferencesImpl.fromContext(context),
  84. clock);
  85. Gson gson = new Gson();
  86. String arbitraryDataString;
  87. MediaFoldersModel mediaFoldersModel;
  88. List<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true);
  89. List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true);
  90. List<String> imageMediaFolderPaths = new ArrayList<>();
  91. List<String> videoMediaFolderPaths = new ArrayList<>();
  92. for (MediaFolder imageMediaFolder : imageMediaFolders) {
  93. imageMediaFolderPaths.add(imageMediaFolder.absolutePath);
  94. }
  95. for (MediaFolder videoMediaFolder : videoMediaFolders) {
  96. imageMediaFolderPaths.add(videoMediaFolder.absolutePath);
  97. }
  98. arbitraryDataString = arbitraryDataProvider.getValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS);
  99. if (!TextUtils.isEmpty(arbitraryDataString)) {
  100. mediaFoldersModel = gson.fromJson(arbitraryDataString, MediaFoldersModel.class);
  101. // merge new detected paths with already notified ones
  102. for (String existingImageFolderPath : mediaFoldersModel.getImageMediaFolders()) {
  103. if (!imageMediaFolderPaths.contains(existingImageFolderPath)) {
  104. imageMediaFolderPaths.add(existingImageFolderPath);
  105. }
  106. }
  107. for (String existingVideoFolderPath : mediaFoldersModel.getVideoMediaFolders()) {
  108. if (!videoMediaFolderPaths.contains(existingVideoFolderPath)) {
  109. videoMediaFolderPaths.add(existingVideoFolderPath);
  110. }
  111. }
  112. // Store updated values
  113. arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS, gson.toJson(new
  114. MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths)));
  115. final AppPreferences preferences = AppPreferencesImpl.fromContext(getContext());
  116. if (preferences.isShowMediaScanNotifications()) {
  117. imageMediaFolderPaths.removeAll(mediaFoldersModel.getImageMediaFolders());
  118. videoMediaFolderPaths.removeAll(mediaFoldersModel.getVideoMediaFolders());
  119. if (!imageMediaFolderPaths.isEmpty() || !videoMediaFolderPaths.isEmpty()) {
  120. List<User> allUsers = userAccountManager.getAllUsers();
  121. List<User> activeUsers = new ArrayList<>();
  122. for (User account : allUsers) {
  123. if (!arbitraryDataProvider.getBooleanValue(account.toPlatformAccount(),
  124. ManageAccountsActivity.PENDING_FOR_REMOVAL)) {
  125. activeUsers.add(account);
  126. }
  127. }
  128. for (User user : activeUsers) {
  129. for (String imageMediaFolder : imageMediaFolderPaths) {
  130. final SyncedFolder folder = syncedFolderProvider.findByLocalPathAndAccount(imageMediaFolder,
  131. user.toPlatformAccount());
  132. if (folder == null) {
  133. String contentTitle = String.format(context.getString(R.string.new_media_folder_detected),
  134. context.getString(R.string.new_media_folder_photos));
  135. sendNotification(contentTitle,
  136. imageMediaFolder.substring(imageMediaFolder.lastIndexOf('/') + 1),
  137. user,
  138. imageMediaFolder,
  139. 1);
  140. }
  141. }
  142. for (String videoMediaFolder : videoMediaFolderPaths) {
  143. final SyncedFolder folder = syncedFolderProvider.findByLocalPathAndAccount(videoMediaFolder,
  144. user.toPlatformAccount());
  145. if (folder == null) {
  146. String contentTitle = String.format(context.getString(R.string.new_media_folder_detected),
  147. context.getString(R.string.new_media_folder_videos));
  148. sendNotification(contentTitle,
  149. videoMediaFolder.substring(videoMediaFolder.lastIndexOf('/') + 1),
  150. user,
  151. videoMediaFolder,
  152. 2);
  153. }
  154. }
  155. }
  156. }
  157. }
  158. } else {
  159. mediaFoldersModel = new MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths);
  160. arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS,
  161. gson.toJson(mediaFoldersModel));
  162. }
  163. return Result.SUCCESS;
  164. }
  165. private void sendNotification(String contentTitle, String subtitle, User user, String path, int type) {
  166. int notificationId = randomId.nextInt();
  167. Context context = getContext();
  168. Intent intent = new Intent(getContext(), SyncedFoldersActivity.class);
  169. intent.putExtra(NOTIFICATION_ID, notificationId);
  170. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  171. intent.putExtra(NotificationJob.KEY_NOTIFICATION_ACCOUNT, user.getAccountName());
  172. intent.putExtra(KEY_MEDIA_FOLDER_PATH, path);
  173. intent.putExtra(KEY_MEDIA_FOLDER_TYPE, type);
  174. intent.putExtra(SyncedFoldersActivity.EXTRA_SHOW_SIDEBAR, true);
  175. PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
  176. NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(
  177. context, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL)
  178. .setSmallIcon(R.drawable.notification_icon)
  179. .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.notification_icon))
  180. .setColor(ThemeUtils.primaryColor(getContext()))
  181. .setSubText(user.getAccountName())
  182. .setContentTitle(contentTitle)
  183. .setContentText(subtitle)
  184. .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
  185. .setAutoCancel(true)
  186. .setContentIntent(pendingIntent);
  187. Intent disableDetection = new Intent(context, NotificationReceiver.class);
  188. disableDetection.putExtra(NOTIFICATION_ID, notificationId);
  189. disableDetection.setAction(DISABLE_DETECTION_CLICK);
  190. PendingIntent disableIntent = PendingIntent.getBroadcast(
  191. context,
  192. notificationId,
  193. disableDetection,
  194. PendingIntent.FLAG_CANCEL_CURRENT
  195. );
  196. notificationBuilder.addAction(
  197. new NotificationCompat.Action(
  198. R.drawable.ic_close,
  199. context.getString(R.string.disable_new_media_folder_detection_notifications),
  200. disableIntent
  201. )
  202. );
  203. PendingIntent configureIntent = PendingIntent.getActivity(
  204. context,
  205. notificationId,
  206. intent,
  207. PendingIntent.FLAG_CANCEL_CURRENT
  208. );
  209. notificationBuilder.addAction(
  210. new NotificationCompat.Action(
  211. R.drawable.ic_settings,
  212. context.getString(R.string.configure_new_media_folder_detection_notifications),
  213. configureIntent
  214. )
  215. );
  216. NotificationManager notificationManager = (NotificationManager)
  217. context.getSystemService(Context.NOTIFICATION_SERVICE);
  218. if (notificationManager != null) {
  219. notificationManager.notify(notificationId, notificationBuilder.build());
  220. }
  221. }
  222. public static class NotificationReceiver extends BroadcastReceiver {
  223. @Override
  224. public void onReceive(Context context, Intent intent) {
  225. String action = intent.getAction();
  226. int notificationId = intent.getIntExtra(NOTIFICATION_ID, 0);
  227. final AppPreferences preferences = AppPreferencesImpl.fromContext(context);
  228. if (DISABLE_DETECTION_CLICK.equals(action)) {
  229. Log_OC.d(this, "Disable media scan notifications");
  230. preferences.setShowMediaScanNotifications(false);
  231. cancel(context, notificationId);
  232. }
  233. }
  234. private void cancel(Context context, int notificationId) {
  235. NotificationManager notificationManager =
  236. (NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
  237. notificationManager.cancel(notificationId);
  238. }
  239. }
  240. }