FolderSyncActivity.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /**
  2. * Nextcloud Android client application
  3. *
  4. * @author Andy Scherzinger
  5. * Copyright (C) 2016 Andy Scherzinger
  6. * Copyright (C) 2016 Nextcloud
  7. * <p>
  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. * <p>
  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. * <p>
  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.content.Intent;
  24. import android.os.Bundle;
  25. import android.os.Handler;
  26. import android.support.annotation.NonNull;
  27. import android.support.design.widget.BottomNavigationView;
  28. import android.support.v4.app.FragmentManager;
  29. import android.support.v4.app.FragmentTransaction;
  30. import android.support.v4.widget.DrawerLayout;
  31. import android.support.v7.widget.GridLayoutManager;
  32. import android.support.v7.widget.RecyclerView;
  33. import android.view.MenuItem;
  34. import android.view.View;
  35. import android.widget.LinearLayout;
  36. import android.widget.TextView;
  37. import com.owncloud.android.MainApp;
  38. import com.owncloud.android.R;
  39. import com.owncloud.android.authentication.AccountUtils;
  40. import com.owncloud.android.datamodel.MediaFolder;
  41. import com.owncloud.android.datamodel.MediaProvider;
  42. import com.owncloud.android.datamodel.OCFile;
  43. import com.owncloud.android.datamodel.SyncedFolder;
  44. import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
  45. import com.owncloud.android.datamodel.SyncedFolderProvider;
  46. import com.owncloud.android.files.services.FileUploader;
  47. import com.owncloud.android.ui.adapter.FolderSyncAdapter;
  48. import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
  49. import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment;
  50. import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable;
  51. import com.owncloud.android.utils.AnalyticsUtils;
  52. import com.owncloud.android.utils.DisplayUtils;
  53. import java.io.File;
  54. import java.util.ArrayList;
  55. import java.util.Collections;
  56. import java.util.Comparator;
  57. import java.util.HashMap;
  58. import java.util.List;
  59. import java.util.Map;
  60. import java.util.TimerTask;
  61. import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID;
  62. /**
  63. * Activity displaying all auto-synced folders and/or instant upload media folders.
  64. */
  65. public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener,
  66. SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener {
  67. private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG";
  68. public static final String PRIORITIZED_FOLDER = "Camera";
  69. public static final String EXTRA_SHOW_SIDEBAR = "SHOW_SIDEBAR";
  70. private static final String SCREEN_NAME = "Auto upload";
  71. private static final String TAG = FolderSyncActivity.class.getSimpleName();
  72. private RecyclerView mRecyclerView;
  73. private FolderSyncAdapter mAdapter;
  74. private LinearLayout mProgress;
  75. private TextView mEmpty;
  76. private SyncedFolderProvider mSyncedFolderProvider;
  77. private List<SyncedFolderDisplayItem> syncFolderItems;
  78. private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment;
  79. private boolean showSidebar = true;
  80. @Override
  81. protected void onCreate(Bundle savedInstanceState) {
  82. super.onCreate(savedInstanceState);
  83. if (getIntent().getExtras() != null) {
  84. showSidebar = getIntent().getExtras().getBoolean(EXTRA_SHOW_SIDEBAR);
  85. }
  86. setContentView(R.layout.folder_sync_layout);
  87. // setup toolbar
  88. setupToolbar();
  89. // setup drawer
  90. setupDrawer(R.id.nav_folder_sync);
  91. if (!showSidebar) {
  92. setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
  93. mDrawerToggle.setDrawerIndicatorEnabled(false);
  94. }
  95. setupContent();
  96. getSupportActionBar().setTitle(getString(R.string.drawer_folder_sync));
  97. getSupportActionBar().setDisplayHomeAsUpEnabled(true);
  98. }
  99. @Override
  100. protected void onResume() {
  101. super.onResume();
  102. AnalyticsUtils.setCurrentScreenName(this, SCREEN_NAME, TAG);
  103. }
  104. /**
  105. * sets up the UI elements and loads all media/synced folders.
  106. */
  107. private void setupContent() {
  108. mRecyclerView = (RecyclerView) findViewById(android.R.id.list);
  109. mProgress = (LinearLayout) findViewById(android.R.id.progress);
  110. mEmpty = (TextView) findViewById(android.R.id.empty);
  111. final int gridWidth = getResources().getInteger(R.integer.media_grid_width);
  112. boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light);
  113. mAdapter = new FolderSyncAdapter(this, gridWidth, this, lightVersion);
  114. mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver());
  115. final GridLayoutManager lm = new GridLayoutManager(this, gridWidth);
  116. mAdapter.setLayoutManager(lm);
  117. int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing);
  118. mRecyclerView.addItemDecoration(new MediaGridItemDecoration(spacing));
  119. mRecyclerView.setLayoutManager(lm);
  120. mRecyclerView.setAdapter(mAdapter);
  121. BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation_view);
  122. if (getResources().getBoolean(R.bool.bottom_toolbar_enabled)) {
  123. bottomNavigationView.setVisibility(View.VISIBLE);
  124. DisplayUtils.setupBottomBar(bottomNavigationView, getResources(), this, -1);
  125. }
  126. load(gridWidth * 2);
  127. }
  128. /**
  129. * loads all media/synced folders, adds them to the recycler view adapter and shows the list.
  130. *
  131. * @param perFolderMediaItemLimit the amount of media items to be loaded/shown per media folder
  132. */
  133. private void load(final int perFolderMediaItemLimit) {
  134. if (mAdapter.getItemCount() > 0) {
  135. return;
  136. }
  137. setListShown(false);
  138. final Handler mHandler = new Handler();
  139. new Thread(new Runnable() {
  140. @Override
  141. public void run() {
  142. final List<MediaFolder> mediaFolders = MediaProvider.getMediaFolders(getContentResolver(),
  143. perFolderMediaItemLimit);
  144. List<SyncedFolder> syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders();
  145. List<SyncedFolder> currentAccountSyncedFoldersList = new ArrayList<SyncedFolder>();
  146. Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(FolderSyncActivity.this);
  147. for (SyncedFolder syncedFolder : syncedFolderArrayList) {
  148. if (syncedFolder.getAccount().equals(currentAccount.name)) {
  149. currentAccountSyncedFoldersList.add(syncedFolder);
  150. }
  151. }
  152. syncFolderItems = sortSyncedFolderItems(mergeFolderData(currentAccountSyncedFoldersList,
  153. mediaFolders));
  154. mHandler.post(new TimerTask() {
  155. @Override
  156. public void run() {
  157. mAdapter.setSyncFolderItems(syncFolderItems);
  158. setListShown(true);
  159. }
  160. });
  161. }
  162. }).start();
  163. }
  164. /**
  165. * merges two lists of {@link SyncedFolder} and {@link MediaFolder} items into one of SyncedFolderItems.
  166. *
  167. * @param syncedFolders the synced folders
  168. * @param mediaFolders the media folders
  169. * @return the merged list of SyncedFolderItems
  170. */
  171. @NonNull
  172. private List<SyncedFolderDisplayItem> mergeFolderData(List<SyncedFolder> syncedFolders,
  173. @NonNull List<MediaFolder> mediaFolders) {
  174. Map<String, SyncedFolder> syncedFoldersMap = createSyncedFoldersMap(syncedFolders);
  175. List<SyncedFolderDisplayItem> result = new ArrayList<>();
  176. for (MediaFolder mediaFolder : mediaFolders) {
  177. if (syncedFoldersMap.containsKey(mediaFolder.absolutePath)) {
  178. SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath);
  179. syncedFoldersMap.remove(mediaFolder.absolutePath);
  180. result.add(createSyncedFolder(syncedFolder, mediaFolder));
  181. } else {
  182. result.add(createSyncedFolderFromMediaFolder(mediaFolder));
  183. }
  184. }
  185. for (SyncedFolder syncedFolder : syncedFoldersMap.values()) {
  186. SyncedFolderDisplayItem syncedFolderDisplayItem = createSyncedFolderWithoutMediaFolder(syncedFolder);
  187. result.add(syncedFolderDisplayItem);
  188. }
  189. return result;
  190. }
  191. /**
  192. * Sorts list of {@link SyncedFolderDisplayItem}s.
  193. *
  194. * @param syncFolderItemList list of items to be sorted
  195. * @return sorted list of items
  196. */
  197. public static List<SyncedFolderDisplayItem> sortSyncedFolderItems(List<SyncedFolderDisplayItem>
  198. syncFolderItemList) {
  199. Collections.sort(syncFolderItemList, new Comparator<SyncedFolderDisplayItem>() {
  200. public int compare(SyncedFolderDisplayItem f1, SyncedFolderDisplayItem f2) {
  201. if (f1 == null && f2 == null) {
  202. return 0;
  203. } else if (f1 == null) {
  204. return -1;
  205. } else if (f2 == null) {
  206. return 1;
  207. } else if (f1.isEnabled() && f2.isEnabled()) {
  208. return f1.getFolderName().toLowerCase().compareTo(f2.getFolderName().toLowerCase());
  209. } else if (f1.isEnabled()) {
  210. return -1;
  211. } else if (f2.isEnabled()) {
  212. return 1;
  213. } else if (f1.getFolderName() == null && f2.getFolderName() == null) {
  214. return 0;
  215. } else if (f1.getFolderName() == null) {
  216. return -1;
  217. } else if (f2.getFolderName() == null) {
  218. return 1;
  219. } else if (PRIORITIZED_FOLDER.equals(f1.getFolderName())) {
  220. return -1;
  221. } else if (PRIORITIZED_FOLDER.equals(f2.getFolderName())) {
  222. return 1;
  223. } else {
  224. return f1.getFolderName().toLowerCase().compareTo(f2.getFolderName().toLowerCase());
  225. }
  226. }
  227. });
  228. return syncFolderItemList;
  229. }
  230. @NonNull
  231. private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) {
  232. return new SyncedFolderDisplayItem(
  233. syncedFolder.getId(),
  234. syncedFolder.getLocalPath(),
  235. syncedFolder.getRemotePath(),
  236. syncedFolder.getWifiOnly(),
  237. syncedFolder.getChargingOnly(),
  238. syncedFolder.getSubfolderByDate(),
  239. syncedFolder.getAccount(),
  240. syncedFolder.getUploadAction(),
  241. syncedFolder.isEnabled(),
  242. new File(syncedFolder.getLocalPath()).getName());
  243. }
  244. /**
  245. * creates a SyncedFolderDisplayItem merging a {@link SyncedFolder} and a {@link MediaFolder} object instance.
  246. *
  247. * @param syncedFolder the synced folder object
  248. * @param mediaFolder the media folder object
  249. * @return the created SyncedFolderDisplayItem
  250. */
  251. @NonNull
  252. private SyncedFolderDisplayItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, @NonNull MediaFolder mediaFolder) {
  253. return new SyncedFolderDisplayItem(
  254. syncedFolder.getId(),
  255. syncedFolder.getLocalPath(),
  256. syncedFolder.getRemotePath(),
  257. syncedFolder.getWifiOnly(),
  258. syncedFolder.getChargingOnly(),
  259. syncedFolder.getSubfolderByDate(),
  260. syncedFolder.getAccount(),
  261. syncedFolder.getUploadAction(),
  262. syncedFolder.isEnabled(),
  263. mediaFolder.filePaths,
  264. mediaFolder.folderName,
  265. mediaFolder.numberOfFiles);
  266. }
  267. /**
  268. * creates a {@link SyncedFolderDisplayItem} based on a {@link MediaFolder} object instance.
  269. *
  270. * @param mediaFolder the media folder object
  271. * @return the created SyncedFolderDisplayItem
  272. */
  273. @NonNull
  274. private SyncedFolderDisplayItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder) {
  275. return new SyncedFolderDisplayItem(
  276. UNPERSISTED_ID,
  277. mediaFolder.absolutePath,
  278. getString(R.string.instant_upload_path) + "/" + mediaFolder.folderName,
  279. true,
  280. false,
  281. false,
  282. AccountUtils.getCurrentOwnCloudAccount(this).name,
  283. FileUploader.LOCAL_BEHAVIOUR_FORGET,
  284. false,
  285. mediaFolder.filePaths,
  286. mediaFolder.folderName,
  287. mediaFolder.numberOfFiles);
  288. }
  289. /**
  290. * creates a lookup map for a list of given {@link SyncedFolder}s with their local path as the key.
  291. *
  292. * @param syncFolders list of {@link SyncedFolder}s
  293. * @return the lookup map for {@link SyncedFolder}s
  294. */
  295. @NonNull
  296. private Map<String, SyncedFolder> createSyncedFoldersMap(List<SyncedFolder> syncFolders) {
  297. Map<String, SyncedFolder> result = new HashMap<>();
  298. if (syncFolders != null) {
  299. for (SyncedFolder syncFolder : syncFolders) {
  300. result.put(syncFolder.getLocalPath(), syncFolder);
  301. }
  302. }
  303. return result;
  304. }
  305. /**
  306. * show/hide recycler view list or the empty message / progress info.
  307. *
  308. * @param shown flag if list should be shown
  309. */
  310. private void setListShown(boolean shown) {
  311. if (mRecyclerView != null) {
  312. mRecyclerView.setVisibility(shown ? View.VISIBLE : View.GONE);
  313. mProgress.setVisibility(shown ? View.GONE : View.VISIBLE);
  314. mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
  315. }
  316. }
  317. @Override
  318. public boolean onOptionsItemSelected(MenuItem item) {
  319. boolean result = true;
  320. switch (item.getItemId()) {
  321. case android.R.id.home: {
  322. if (showSidebar) {
  323. if (isDrawerOpen()) {
  324. closeDrawer();
  325. } else {
  326. openDrawer();
  327. }
  328. } else {
  329. Intent settingsIntent = new Intent(getApplicationContext(), Preferences.class);
  330. startActivity(settingsIntent);
  331. }
  332. break;
  333. }
  334. default:
  335. result = super.onOptionsItemSelected(item);
  336. }
  337. return result;
  338. }
  339. @Override
  340. public void restart() {
  341. Intent i = new Intent(this, FileDisplayActivity.class);
  342. i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  343. startActivity(i);
  344. }
  345. @Override
  346. public void showFiles(boolean onDeviceOnly) {
  347. MainApp.showOnlyFilesOnDevice(onDeviceOnly);
  348. Intent fileDisplayActivity = new Intent(getApplicationContext(), FileDisplayActivity.class);
  349. fileDisplayActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  350. startActivity(fileDisplayActivity);
  351. }
  352. @Override
  353. public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) {
  354. if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) {
  355. mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderDisplayItem.getId(),
  356. syncedFolderDisplayItem.isEnabled());
  357. } else {
  358. long storedId = mSyncedFolderProvider.storeFolderSync(syncedFolderDisplayItem);
  359. if (storedId != -1) {
  360. syncedFolderDisplayItem.setId(storedId);
  361. }
  362. }
  363. }
  364. @Override
  365. public void onSyncFolderSettingsClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) {
  366. FragmentManager fm = getSupportFragmentManager();
  367. FragmentTransaction ft = fm.beginTransaction();
  368. ft.addToBackStack(null);
  369. mSyncedFolderPreferencesDialogFragment = SyncedFolderPreferencesDialogFragment.newInstance(
  370. syncedFolderDisplayItem, section);
  371. mSyncedFolderPreferencesDialogFragment.show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG);
  372. }
  373. @Override
  374. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  375. if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_REMOTE_FOLDER
  376. && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) {
  377. OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
  378. mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath());
  379. } else {
  380. super.onActivityResult(requestCode, resultCode, data);
  381. }
  382. }
  383. @Override
  384. public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
  385. SyncedFolderDisplayItem item = syncFolderItems.get(syncedFolder.getSection());
  386. boolean dirty = item.isEnabled() != syncedFolder.getEnabled();
  387. item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder
  388. .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder
  389. .getUploadAction(), syncedFolder.getEnabled());
  390. if (syncedFolder.getId() == UNPERSISTED_ID) {
  391. // newly set up folder sync config
  392. long storedId = mSyncedFolderProvider.storeFolderSync(item);
  393. if (storedId != -1) {
  394. item.setId(storedId);
  395. }
  396. } else {
  397. // existing synced folder setup to be updated
  398. mSyncedFolderProvider.updateSyncFolder(item);
  399. }
  400. mSyncedFolderPreferencesDialogFragment = null;
  401. if (dirty) {
  402. mAdapter.setSyncFolderItem(syncedFolder.getSection(), item);
  403. }
  404. }
  405. @Override
  406. public void onCancelSyncedFolderPreference() {
  407. mSyncedFolderPreferencesDialogFragment = null;
  408. }
  409. /**
  410. * update given synced folder with the given values.
  411. *
  412. * @param item the synced folder to be updated
  413. * @param localPath the local path
  414. * @param remotePath the remote path
  415. * @param wifiOnly upload on wifi only
  416. * @param chargingOnly upload on charging only
  417. * @param subfolderByDate created sub folders
  418. * @param uploadAction upload action
  419. * @param enabled is sync enabled
  420. * @return the updated item
  421. */
  422. private SyncedFolderDisplayItem updateSyncedFolderItem(SyncedFolderDisplayItem item,
  423. String localPath,
  424. String remotePath,
  425. Boolean wifiOnly,
  426. Boolean chargingOnly,
  427. Boolean subfolderByDate,
  428. Integer uploadAction,
  429. Boolean enabled) {
  430. item.setLocalPath(localPath);
  431. item.setRemotePath(remotePath);
  432. item.setWifiOnly(wifiOnly);
  433. item.setChargingOnly(chargingOnly);
  434. item.setSubfolderByDate(subfolderByDate);
  435. item.setUploadAction(uploadAction);
  436. item.setEnabled(enabled);
  437. return item;
  438. }
  439. }