SyncedFoldersActivity.java 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Andy Scherzinger
  5. * Copyright (C) 2016 Andy Scherzinger
  6. * Copyright (C) 2016 Nextcloud
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. package com.owncloud.android.ui.activity;
  22. import android.accounts.Account;
  23. import android.annotation.SuppressLint;
  24. import android.app.Activity;
  25. import android.app.NotificationManager;
  26. import android.content.Context;
  27. import android.content.Intent;
  28. import android.content.pm.PackageManager;
  29. import android.net.Uri;
  30. import android.os.Build;
  31. import android.os.Bundle;
  32. import android.os.PowerManager;
  33. import android.text.TextUtils;
  34. import android.util.Log;
  35. import android.view.Menu;
  36. import android.view.MenuInflater;
  37. import android.view.MenuItem;
  38. import android.view.View;
  39. import android.widget.LinearLayout;
  40. import android.widget.TextView;
  41. import com.google.android.material.bottomnavigation.BottomNavigationView;
  42. import com.nextcloud.client.di.Injectable;
  43. import com.nextcloud.client.preferences.AppPreferences;
  44. import com.nextcloud.client.preferences.PreferenceManager;
  45. import com.owncloud.android.BuildConfig;
  46. import com.owncloud.android.MainApp;
  47. import com.owncloud.android.R;
  48. import com.owncloud.android.authentication.AccountUtils;
  49. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  50. import com.owncloud.android.datamodel.MediaFolder;
  51. import com.owncloud.android.datamodel.MediaFolderType;
  52. import com.owncloud.android.datamodel.MediaProvider;
  53. import com.owncloud.android.datamodel.OCFile;
  54. import com.owncloud.android.datamodel.SyncedFolder;
  55. import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
  56. import com.owncloud.android.datamodel.SyncedFolderProvider;
  57. import com.owncloud.android.files.services.FileUploader;
  58. import com.owncloud.android.jobs.MediaFoldersDetectionJob;
  59. import com.owncloud.android.jobs.NotificationJob;
  60. import com.owncloud.android.ui.adapter.SyncedFolderAdapter;
  61. import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
  62. import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment;
  63. import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable;
  64. import com.owncloud.android.utils.DisplayUtils;
  65. import com.owncloud.android.utils.FilesSyncHelper;
  66. import com.owncloud.android.utils.PermissionUtil;
  67. import com.owncloud.android.utils.ThemeUtils;
  68. import java.io.File;
  69. import java.util.ArrayList;
  70. import java.util.Arrays;
  71. import java.util.Collections;
  72. import java.util.HashMap;
  73. import java.util.List;
  74. import java.util.Locale;
  75. import java.util.Map;
  76. import javax.inject.Inject;
  77. import androidx.annotation.NonNull;
  78. import androidx.appcompat.app.ActionBar;
  79. import androidx.appcompat.app.AlertDialog;
  80. import androidx.drawerlayout.widget.DrawerLayout;
  81. import androidx.fragment.app.FragmentManager;
  82. import androidx.fragment.app.FragmentTransaction;
  83. import androidx.recyclerview.widget.GridLayoutManager;
  84. import androidx.recyclerview.widget.RecyclerView;
  85. import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
  86. import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID;
  87. /**
  88. * Activity displaying all auto-synced folders and/or instant upload media folders.
  89. */
  90. public class SyncedFoldersActivity extends FileActivity implements SyncedFolderAdapter.ClickListener,
  91. SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener, Injectable {
  92. private static final String[] PRIORITIZED_FOLDERS = new String[]{"Camera", "Screenshots"};
  93. private static final List<String> SPECIAL_MANUFACTURER = Arrays.asList("Samsung", "Huawei", "Xiaomi");
  94. public static final String EXTRA_SHOW_SIDEBAR = "SHOW_SIDEBAR";
  95. private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG";
  96. private static final String TAG = SyncedFoldersActivity.class.getSimpleName();
  97. private RecyclerView mRecyclerView;
  98. private SyncedFolderAdapter mAdapter;
  99. private LinearLayout mProgress;
  100. private TextView mEmpty;
  101. private SyncedFolderProvider mSyncedFolderProvider;
  102. private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment;
  103. private boolean showSidebar = true;
  104. private String path;
  105. private int type;
  106. @Inject AppPreferences preferences;
  107. @Override
  108. protected void onCreate(Bundle savedInstanceState) {
  109. super.onCreate(savedInstanceState);
  110. if (getIntent().getExtras() != null) {
  111. showSidebar = getIntent().getExtras().getBoolean(EXTRA_SHOW_SIDEBAR);
  112. }
  113. setContentView(R.layout.synced_folders_layout);
  114. String account;
  115. Account currentAccount;
  116. if (getIntent() != null && getIntent().getExtras() != null) {
  117. account = getIntent().getExtras().getString(NotificationJob.KEY_NOTIFICATION_ACCOUNT);
  118. currentAccount = getAccount();
  119. if (account != null && currentAccount != null && !account.equalsIgnoreCase(currentAccount.name)) {
  120. AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), account);
  121. setAccount(AccountUtils.getCurrentOwnCloudAccount(this));
  122. }
  123. path = getIntent().getStringExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_PATH);
  124. type = getIntent().getIntExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_TYPE, -1);
  125. // Cancel notification
  126. int notificationId = getIntent().getIntExtra(MediaFoldersDetectionJob.NOTIFICATION_ID, 0);
  127. NotificationManager notificationManager =
  128. (NotificationManager) getSystemService(Activity.NOTIFICATION_SERVICE);
  129. notificationManager.cancel(notificationId);
  130. }
  131. // setup toolbar
  132. setupToolbar();
  133. if (getSupportActionBar() != null){
  134. getSupportActionBar().setTitle(R.string.drawer_synced_folders);
  135. }
  136. // setup drawer
  137. setupDrawer(R.id.nav_synced_folders);
  138. if (!showSidebar) {
  139. setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
  140. mDrawerToggle.setDrawerIndicatorEnabled(false);
  141. }
  142. setupContent();
  143. ActionBar actionBar = getSupportActionBar();
  144. if (actionBar != null) {
  145. ThemeUtils.setColoredTitle(getSupportActionBar(), getString(R.string.drawer_synced_folders), this);
  146. actionBar.setDisplayHomeAsUpEnabled(true);
  147. }
  148. if (ThemeUtils.themingEnabled(this)) {
  149. setTheme(R.style.FallbackThemingTheme);
  150. }
  151. }
  152. @Override
  153. public boolean onCreateOptionsMenu(Menu menu) {
  154. MenuInflater inflater = getMenuInflater();
  155. inflater.inflate(R.menu.synced_folders_menu, menu);
  156. return true;
  157. }
  158. /**
  159. * sets up the UI elements and loads all media/synced folders.
  160. */
  161. private void setupContent() {
  162. mRecyclerView = findViewById(android.R.id.list);
  163. mProgress = findViewById(android.R.id.progress);
  164. mEmpty = findViewById(android.R.id.empty);
  165. final int gridWidth = getResources().getInteger(R.integer.media_grid_width);
  166. boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light);
  167. mAdapter = new SyncedFolderAdapter(this, gridWidth, this, lightVersion);
  168. mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver(), preferences);
  169. final GridLayoutManager lm = new GridLayoutManager(this, gridWidth);
  170. mAdapter.setLayoutManager(lm);
  171. int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing);
  172. mRecyclerView.addItemDecoration(new MediaGridItemDecoration(spacing));
  173. mRecyclerView.setLayoutManager(lm);
  174. mRecyclerView.setAdapter(mAdapter);
  175. BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation_view);
  176. if (getResources().getBoolean(R.bool.bottom_toolbar_enabled)) {
  177. bottomNavigationView.setVisibility(View.VISIBLE);
  178. DisplayUtils.setupBottomBar(bottomNavigationView, getResources(), this, -1);
  179. }
  180. load(gridWidth * 2, false);
  181. }
  182. /**
  183. * loads all media/synced folders, adds them to the recycler view adapter and shows the list.
  184. *
  185. * @param perFolderMediaItemLimit the amount of media items to be loaded/shown per media folder
  186. */
  187. private void load(final int perFolderMediaItemLimit, boolean force) {
  188. if (mAdapter.getItemCount() > 0 && !force) {
  189. return;
  190. }
  191. setListShown(false);
  192. final List<MediaFolder> mediaFolders = MediaProvider.getImageFolders(getContentResolver(),
  193. perFolderMediaItemLimit, this, false);
  194. mediaFolders.addAll(MediaProvider.getVideoFolders(getContentResolver(), perFolderMediaItemLimit,
  195. this, false));
  196. List<SyncedFolder> syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders();
  197. List<SyncedFolder> currentAccountSyncedFoldersList = new ArrayList<>();
  198. Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(this);
  199. for (SyncedFolder syncedFolder : syncedFolderArrayList) {
  200. if (currentAccount != null && syncedFolder.getAccount().equals(currentAccount.name)) {
  201. // delete non-existing & disabled synced folders
  202. if (!new File(syncedFolder.getLocalPath()).exists() && !syncedFolder.isEnabled()) {
  203. mSyncedFolderProvider.deleteSyncedFolder(syncedFolder.getId());
  204. } else {
  205. currentAccountSyncedFoldersList.add(syncedFolder);
  206. }
  207. }
  208. }
  209. List<SyncedFolderDisplayItem> syncFolderItems = sortSyncedFolderItems(
  210. mergeFolderData(currentAccountSyncedFoldersList, mediaFolders));
  211. mAdapter.setSyncFolderItems(syncFolderItems);
  212. mAdapter.notifyDataSetChanged();
  213. setListShown(true);
  214. if (!TextUtils.isEmpty(path)) {
  215. int section = mAdapter.getSectionByLocalPathAndType(path, type);
  216. if (section >= 0) {
  217. onSyncFolderSettingsClick(section, mAdapter.get(section));
  218. }
  219. }
  220. }
  221. /**
  222. * Sorts list of {@link SyncedFolderDisplayItem}s.
  223. *
  224. * @param syncFolderItemList list of items to be sorted
  225. * @return sorted list of items
  226. */
  227. public static List<SyncedFolderDisplayItem> sortSyncedFolderItems(List<SyncedFolderDisplayItem>
  228. syncFolderItemList) {
  229. Collections.sort(syncFolderItemList, (f1, f2) -> {
  230. if (f1 == null && f2 == null) {
  231. return 0;
  232. } else if (f1 == null) {
  233. return -1;
  234. } else if (f2 == null) {
  235. return 1;
  236. } else if (f1.isEnabled() && f2.isEnabled()) {
  237. return f1.getFolderName().toLowerCase(Locale.getDefault()).compareTo(
  238. f2.getFolderName().toLowerCase(Locale.getDefault()));
  239. } else if (f1.isEnabled()) {
  240. return -1;
  241. } else if (f2.isEnabled()) {
  242. return 1;
  243. } else if (f1.getFolderName() == null && f2.getFolderName() == null) {
  244. return 0;
  245. } else if (f1.getFolderName() == null) {
  246. return -1;
  247. } else if (f2.getFolderName() == null) {
  248. return 1;
  249. }
  250. for (String folder : PRIORITIZED_FOLDERS) {
  251. if (folder.equals(f1.getFolderName()) && folder.equals(f2.getFolderName())) {
  252. return 0;
  253. } else if (folder.equals(f1.getFolderName())) {
  254. return -1;
  255. } else if (folder.equals(f2.getFolderName())) {
  256. return 1;
  257. }
  258. }
  259. return f1.getFolderName().toLowerCase(Locale.getDefault()).compareTo(
  260. f2.getFolderName().toLowerCase(Locale.getDefault()));
  261. });
  262. return syncFolderItemList;
  263. }
  264. /**
  265. * merges two lists of {@link SyncedFolder} and {@link MediaFolder} items into one of SyncedFolderItems.
  266. *
  267. * @param syncedFolders the synced folders
  268. * @param mediaFolders the media folders
  269. * @return the merged list of SyncedFolderItems
  270. */
  271. @NonNull
  272. private List<SyncedFolderDisplayItem> mergeFolderData(List<SyncedFolder> syncedFolders,
  273. @NonNull List<MediaFolder> mediaFolders) {
  274. Map<String, SyncedFolder> syncedFoldersMap = createSyncedFoldersMap(syncedFolders);
  275. List<SyncedFolderDisplayItem> result = new ArrayList<>();
  276. for (MediaFolder mediaFolder : mediaFolders) {
  277. if (syncedFoldersMap.containsKey(mediaFolder.absolutePath + "-" + mediaFolder.type)) {
  278. SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath + "-" + mediaFolder.type);
  279. syncedFoldersMap.remove(mediaFolder.absolutePath + "-" + mediaFolder.type);
  280. if (MediaFolderType.CUSTOM == syncedFolder.getType()) {
  281. result.add(createSyncedFolderWithoutMediaFolder(syncedFolder));
  282. } else {
  283. result.add(createSyncedFolder(syncedFolder, mediaFolder));
  284. }
  285. } else {
  286. result.add(createSyncedFolderFromMediaFolder(mediaFolder));
  287. }
  288. }
  289. for (SyncedFolder syncedFolder : syncedFoldersMap.values()) {
  290. result.add(createSyncedFolderWithoutMediaFolder(syncedFolder));
  291. }
  292. return result;
  293. }
  294. @NonNull
  295. private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) {
  296. File localFolder = new File(syncedFolder.getLocalPath());
  297. File[] files = getFileList(localFolder);
  298. List<String> filePaths = getDisplayFilePathList(files);
  299. return new SyncedFolderDisplayItem(
  300. syncedFolder.getId(),
  301. syncedFolder.getLocalPath(),
  302. syncedFolder.getRemotePath(),
  303. syncedFolder.getWifiOnly(),
  304. syncedFolder.getChargingOnly(),
  305. syncedFolder.getSubfolderByDate(),
  306. syncedFolder.getAccount(),
  307. syncedFolder.getUploadAction(),
  308. syncedFolder.isEnabled(),
  309. filePaths,
  310. localFolder.getName(),
  311. files.length,
  312. syncedFolder.getType());
  313. }
  314. /**
  315. * creates a SyncedFolderDisplayItem merging a {@link SyncedFolder} and a {@link MediaFolder} object instance.
  316. *
  317. * @param syncedFolder the synced folder object
  318. * @param mediaFolder the media folder object
  319. * @return the created SyncedFolderDisplayItem
  320. */
  321. @NonNull
  322. private SyncedFolderDisplayItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, @NonNull MediaFolder mediaFolder) {
  323. return new SyncedFolderDisplayItem(
  324. syncedFolder.getId(),
  325. syncedFolder.getLocalPath(),
  326. syncedFolder.getRemotePath(),
  327. syncedFolder.getWifiOnly(),
  328. syncedFolder.getChargingOnly(),
  329. syncedFolder.getSubfolderByDate(),
  330. syncedFolder.getAccount(),
  331. syncedFolder.getUploadAction(),
  332. syncedFolder.isEnabled(),
  333. mediaFolder.filePaths,
  334. mediaFolder.folderName,
  335. mediaFolder.numberOfFiles,
  336. mediaFolder.type);
  337. }
  338. /**
  339. * creates a {@link SyncedFolderDisplayItem} based on a {@link MediaFolder} object instance.
  340. *
  341. * @param mediaFolder the media folder object
  342. * @return the created SyncedFolderDisplayItem
  343. */
  344. @NonNull
  345. private SyncedFolderDisplayItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder) {
  346. return new SyncedFolderDisplayItem(
  347. UNPERSISTED_ID,
  348. mediaFolder.absolutePath,
  349. getString(R.string.instant_upload_path) + "/" + mediaFolder.folderName,
  350. true,
  351. false,
  352. false,
  353. getAccount().name,
  354. FileUploader.LOCAL_BEHAVIOUR_FORGET,
  355. false,
  356. mediaFolder.filePaths,
  357. mediaFolder.folderName,
  358. mediaFolder.numberOfFiles,
  359. mediaFolder.type);
  360. }
  361. private File[] getFileList(File localFolder) {
  362. File[] files = localFolder.listFiles(pathname -> !pathname.isDirectory());
  363. if (files != null) {
  364. Arrays.sort(files, (f1, f2) -> Long.compare(f1.lastModified(), f2.lastModified()));
  365. } else {
  366. files = new File[]{};
  367. }
  368. return files;
  369. }
  370. private List<String> getDisplayFilePathList(File... files) {
  371. List<String> filePaths = null;
  372. if (files != null && files.length > 0) {
  373. filePaths = new ArrayList<>();
  374. for (int i = 0; i < 7 && i < files.length; i++) {
  375. filePaths.add(files[i].getAbsolutePath());
  376. }
  377. }
  378. return filePaths;
  379. }
  380. /**
  381. * creates a lookup map for a list of given {@link SyncedFolder}s with their local path as the key.
  382. *
  383. * @param syncFolders list of {@link SyncedFolder}s
  384. * @return the lookup map for {@link SyncedFolder}s
  385. */
  386. @NonNull
  387. private Map<String, SyncedFolder> createSyncedFoldersMap(List<SyncedFolder> syncFolders) {
  388. Map<String, SyncedFolder> result = new HashMap<>();
  389. if (syncFolders != null) {
  390. for (SyncedFolder syncFolder : syncFolders) {
  391. result.put(syncFolder.getLocalPath() + "-" + syncFolder.getType(), syncFolder);
  392. }
  393. }
  394. return result;
  395. }
  396. /**
  397. * show/hide recycler view list or the empty message / progress info.
  398. *
  399. * @param shown flag if list should be shown
  400. */
  401. private void setListShown(boolean shown) {
  402. if (mRecyclerView != null) {
  403. mRecyclerView.setVisibility(shown ? View.VISIBLE : View.GONE);
  404. mProgress.setVisibility(shown ? View.GONE : View.VISIBLE);
  405. mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
  406. }
  407. }
  408. @Override
  409. public boolean onOptionsItemSelected(MenuItem item) {
  410. boolean result = true;
  411. switch (item.getItemId()) {
  412. case android.R.id.home: {
  413. if (showSidebar) {
  414. if (isDrawerOpen()) {
  415. closeDrawer();
  416. } else {
  417. openDrawer();
  418. }
  419. } else {
  420. Intent settingsIntent = new Intent(getApplicationContext(), SettingsActivity.class);
  421. startActivity(settingsIntent);
  422. }
  423. break;
  424. }
  425. case R.id.action_create_custom_folder: {
  426. Log.d(TAG, "Show custom folder dialog");
  427. SyncedFolderDisplayItem emptyCustomFolder = new SyncedFolderDisplayItem(
  428. SyncedFolder.UNPERSISTED_ID, null, null, true, false,
  429. false, getAccount().name,
  430. FileUploader.LOCAL_BEHAVIOUR_FORGET, false, null, MediaFolderType.CUSTOM);
  431. onSyncFolderSettingsClick(0, emptyCustomFolder);
  432. }
  433. default:
  434. result = super.onOptionsItemSelected(item);
  435. break;
  436. }
  437. return result;
  438. }
  439. @Override
  440. public void restart() {
  441. Intent i = new Intent(this, FileDisplayActivity.class);
  442. i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  443. startActivity(i);
  444. }
  445. @Override
  446. public void showFiles(boolean onDeviceOnly) {
  447. MainApp.showOnlyFilesOnDevice(onDeviceOnly);
  448. Intent fileDisplayActivity = new Intent(getApplicationContext(), FileDisplayActivity.class);
  449. fileDisplayActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  450. startActivity(fileDisplayActivity);
  451. }
  452. @Override
  453. public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) {
  454. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
  455. getContentResolver());
  456. if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) {
  457. mSyncedFolderProvider.updateSyncedFolderEnabled(syncedFolderDisplayItem.getId(),
  458. syncedFolderDisplayItem.isEnabled());
  459. } else {
  460. long storedId = mSyncedFolderProvider.storeSyncedFolder(syncedFolderDisplayItem);
  461. if (storedId != -1) {
  462. syncedFolderDisplayItem.setId(storedId);
  463. }
  464. }
  465. if (syncedFolderDisplayItem.isEnabled()) {
  466. FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem);
  467. showBatteryOptimizationInfo();
  468. } else {
  469. String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolderDisplayItem.getId();
  470. arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
  471. }
  472. }
  473. @Override
  474. public void onSyncFolderSettingsClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) {
  475. FragmentManager fm = getSupportFragmentManager();
  476. FragmentTransaction ft = fm.beginTransaction();
  477. ft.addToBackStack(null);
  478. mSyncedFolderPreferencesDialogFragment = SyncedFolderPreferencesDialogFragment.newInstance(
  479. syncedFolderDisplayItem, section);
  480. mSyncedFolderPreferencesDialogFragment.show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG);
  481. }
  482. @Override
  483. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  484. if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_REMOTE_FOLDER
  485. && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) {
  486. OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
  487. mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath());
  488. }
  489. if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_LOCAL_FOLDER
  490. && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) {
  491. String localPath = data.getStringExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);
  492. mSyncedFolderPreferencesDialogFragment.setLocalFolderSummary(localPath);
  493. } else {
  494. super.onActivityResult(requestCode, resultCode, data);
  495. }
  496. }
  497. @Override
  498. public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
  499. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
  500. getContentResolver());
  501. // custom folders newly created aren't in the list already,
  502. // so triggering a refresh
  503. if (MediaFolderType.CUSTOM.equals(syncedFolder.getType()) && syncedFolder.getId() == UNPERSISTED_ID) {
  504. SyncedFolderDisplayItem newCustomFolder = new SyncedFolderDisplayItem(
  505. SyncedFolder.UNPERSISTED_ID, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(),
  506. syncedFolder.getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(),
  507. syncedFolder.getAccount(), syncedFolder.getUploadAction(), syncedFolder.getEnabled(),
  508. new File(syncedFolder.getLocalPath()).getName(), syncedFolder.getType());
  509. long storedId = mSyncedFolderProvider.storeSyncedFolder(newCustomFolder);
  510. if (storedId != -1) {
  511. newCustomFolder.setId(storedId);
  512. if (newCustomFolder.isEnabled()) {
  513. FilesSyncHelper.insertAllDBEntriesForSyncedFolder(newCustomFolder);
  514. } else {
  515. String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + newCustomFolder.getId();
  516. arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
  517. }
  518. }
  519. mAdapter.addSyncFolderItem(newCustomFolder);
  520. } else {
  521. SyncedFolderDisplayItem item = mAdapter.get(syncedFolder.getSection());
  522. item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder
  523. .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder
  524. .getUploadAction(), syncedFolder.getEnabled());
  525. if (syncedFolder.getId() == UNPERSISTED_ID) {
  526. // newly set up folder sync config
  527. long storedId = mSyncedFolderProvider.storeSyncedFolder(item);
  528. if (storedId != -1) {
  529. item.setId(storedId);
  530. if (item.isEnabled()) {
  531. FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item);
  532. } else {
  533. String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
  534. arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
  535. }
  536. }
  537. } else {
  538. // existing synced folder setup to be updated
  539. mSyncedFolderProvider.updateSyncFolder(item);
  540. if (item.isEnabled()) {
  541. FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item);
  542. } else {
  543. String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
  544. arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
  545. }
  546. }
  547. mAdapter.setSyncFolderItem(syncedFolder.getSection(), item);
  548. }
  549. mSyncedFolderPreferencesDialogFragment = null;
  550. if (syncedFolder.getEnabled()) {
  551. showBatteryOptimizationInfo();
  552. }
  553. }
  554. @Override
  555. public void onCancelSyncedFolderPreference() {
  556. mSyncedFolderPreferencesDialogFragment = null;
  557. }
  558. @Override
  559. public void onDeleteSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
  560. mSyncedFolderProvider.deleteSyncedFolder(syncedFolder.getId());
  561. mAdapter.removeItem(syncedFolder.getSection());
  562. }
  563. /**
  564. * update given synced folder with the given values.
  565. *
  566. * @param item the synced folder to be updated
  567. * @param localPath the local path
  568. * @param remotePath the remote path
  569. * @param wifiOnly upload on wifi only
  570. * @param chargingOnly upload on charging only
  571. * @param subfolderByDate created sub folders
  572. * @param uploadAction upload action
  573. * @param enabled is sync enabled
  574. * @return the updated item
  575. */
  576. private SyncedFolderDisplayItem updateSyncedFolderItem(SyncedFolderDisplayItem item,
  577. String localPath,
  578. String remotePath,
  579. Boolean wifiOnly,
  580. Boolean chargingOnly,
  581. Boolean subfolderByDate,
  582. Integer uploadAction,
  583. Boolean enabled) {
  584. item.setLocalPath(localPath);
  585. item.setRemotePath(remotePath);
  586. item.setWifiOnly(wifiOnly);
  587. item.setChargingOnly(chargingOnly);
  588. item.setSubfolderByDate(subfolderByDate);
  589. item.setUploadAction(uploadAction);
  590. item.setEnabled(enabled);
  591. return item;
  592. }
  593. @Override
  594. public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
  595. @NonNull int[] grantResults) {
  596. switch (requestCode) {
  597. case PermissionUtil.PERMISSIONS_WRITE_EXTERNAL_STORAGE: {
  598. // If request is cancelled, result arrays are empty.
  599. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  600. // permission was granted
  601. int gridWidth = getResources().getInteger(R.integer.media_grid_width);
  602. load(gridWidth * 2, true);
  603. } else {
  604. // permission denied --> do nothing
  605. return;
  606. }
  607. return;
  608. }
  609. default:
  610. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  611. }
  612. }
  613. @Override
  614. protected void onResume() {
  615. super.onResume();
  616. setDrawerMenuItemChecked(R.id.nav_synced_folders);
  617. }
  618. private void showBatteryOptimizationInfo() {
  619. boolean isSpecialManufacturer = SPECIAL_MANUFACTURER.contains(Build.MANUFACTURER.toLowerCase(Locale.ROOT));
  620. if (isSpecialManufacturer && checkIfBatteryOptimizationEnabled() || checkIfBatteryOptimizationEnabled()) {
  621. AlertDialog alertDialog = new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog)
  622. .setTitle(getString(R.string.battery_optimization_title))
  623. .setMessage(getString(R.string.battery_optimization_message))
  624. .setPositiveButton(getString(R.string.battery_optimization_disable), (dialog, which) -> {
  625. // show instant upload
  626. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  627. @SuppressLint("BatteryLife")
  628. Intent intent = new Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
  629. Uri.parse("package:" + BuildConfig.APPLICATION_ID));
  630. if (intent.resolveActivity(getPackageManager()) != null) {
  631. startActivity(intent);
  632. }
  633. } else {
  634. Intent powerUsageIntent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
  635. if (getPackageManager().resolveActivity(powerUsageIntent, 0) != null) {
  636. startActivity(powerUsageIntent);
  637. } else {
  638. dialog.dismiss();
  639. DisplayUtils.showSnackMessage(this, getString(R.string.battery_optimization_no_setting));
  640. }
  641. }
  642. })
  643. .setNegativeButton(getString(R.string.battery_optimization_close), (dialog, which) -> dialog.dismiss())
  644. .setIcon(R.drawable.ic_battery_alert)
  645. .show();
  646. int color = ThemeUtils.primaryAccentColor(this);
  647. alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
  648. alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
  649. }
  650. }
  651. /**
  652. * Check if battery optimization is enabled. If unknown, fallback to true.
  653. *
  654. * @return true if battery optimization is enabled
  655. */
  656. private boolean checkIfBatteryOptimizationEnabled() {
  657. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  658. PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
  659. if (powerManager == null) {
  660. return true;
  661. }
  662. return !powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID);
  663. } else {
  664. return true;
  665. }
  666. }
  667. }