SyncedFoldersActivity.java 32 KB

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