Merge master

Signed-off-by: alperozturk <alper_ozturk@proton.me>
This commit is contained in:
alperozturk 2023-10-26 12:58:10 +02:00
commit 24136e1698
No known key found for this signature in database
GPG key ID: 4E577DC593B59BDF
16 changed files with 760 additions and 724 deletions

View file

@ -19,7 +19,7 @@ jobs:
matrix:
flavor: [ Generic, Gplay, Huawei ]
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:

View file

@ -19,7 +19,7 @@ jobs:
matrix:
task: [ detekt, spotlessKotlinCheck ]
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:

View file

@ -12,6 +12,10 @@ on:
permissions:
contents: read
concurrency:
group: code-ql-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze

View file

@ -23,7 +23,7 @@ jobs:
steps:
- name: Add reaction on start
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
repository: ${{ github.event.repository.full_name }}
@ -31,7 +31,7 @@ jobs:
reaction-type: "+1"
- name: Checkout the latest code
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
token: ${{ secrets.COMMAND_BOT_PAT }}
@ -42,7 +42,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
- name: Add reaction on failure
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
if: failure()
with:
token: ${{ secrets.COMMAND_BOT_PAT }}

View file

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:

View file

@ -19,7 +19,7 @@ jobs:
- name: Check if secrets are available
run: echo "::set-output name=ok::${{ secrets.KS_PASS != '' }}"
id: check-secrets
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
if: ${{ steps.check-secrets.outputs.ok == 'true' }}
- name: set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3

View file

@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif

View file

@ -22,7 +22,7 @@ jobs:
color: [ blue ]
api-level: [ 27 ]
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Gradle cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3

View file

@ -18,7 +18,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
with:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,691 +0,0 @@
/*
* Nextcloud Android client application
*
* @author Mario Danic
* @author TSI-mc
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2017 Nextcloud GmbH.
* Copyright (C) 2023 TSI-mc
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.fragment.contactsbackup;
import android.Manifest;
import android.app.DatePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.Toast;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.java.util.Optional;
import com.owncloud.android.R;
import com.owncloud.android.databinding.BackupFragmentBinding;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
import com.owncloud.android.ui.activity.SettingsActivity;
import com.owncloud.android.ui.fragment.FileFragment;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.PermissionUtil;
import com.owncloud.android.utils.theme.ThemeUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import third_parties.daveKoeller.AlphanumComparator;
import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP;
import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP;
public class BackupFragment extends FileFragment implements DatePickerDialog.OnDateSetListener, Injectable {
public static final String TAG = BackupFragment.class.getSimpleName();
private static final String ARG_SHOW_SIDEBAR = "SHOW_SIDEBAR";
private static final String KEY_CALENDAR_PICKER_OPEN = "IS_CALENDAR_PICKER_OPEN";
private static final String KEY_CALENDAR_DAY = "CALENDAR_DAY";
private static final String KEY_CALENDAR_MONTH = "CALENDAR_MONTH";
private static final String KEY_CALENDAR_YEAR = "CALENDAR_YEAR";
public static final String PREFERENCE_CONTACTS_BACKUP_ENABLED = "PREFERENCE_CONTACTS_BACKUP_ENABLED";
public static final String PREFERENCE_CALENDAR_BACKUP_ENABLED = "PREFERENCE_CALENDAR_BACKUP_ENABLED";
private BackupFragmentBinding binding;
@Inject BackgroundJobManager backgroundJobManager;
@Inject ThemeUtils themeUtils;
@Inject ArbitraryDataProvider arbitraryDataProvider;
@Inject ViewThemeUtils viewThemeUtils;
private Date selectedDate;
private boolean calendarPickerOpen;
private DatePickerDialog datePickerDialog;
private CompoundButton.OnCheckedChangeListener dailyBackupCheckedChangeListener;
private CompoundButton.OnCheckedChangeListener contactsCheckedListener;
private CompoundButton.OnCheckedChangeListener calendarCheckedListener;
private User user;
private boolean showSidebar = true;
//flag to check if calendar backup should be shown and backup should be done or not
private boolean showCalendarBackup = true;
public static BackupFragment create(boolean showSidebar) {
BackupFragment fragment = new BackupFragment();
Bundle bundle = new Bundle();
bundle.putBoolean(ARG_SHOW_SIDEBAR, showSidebar);
fragment.setArguments(bundle);
return fragment;
}
private boolean isCalendarBackupEnabled() {
return arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CALENDAR_BACKUP_ENABLED);
}
private void setCalendarBackupEnabled(final boolean enabled) {
arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREFERENCE_CALENDAR_BACKUP_ENABLED, enabled);
}
private boolean isContactsBackupEnabled() {
return arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CONTACTS_BACKUP_ENABLED);
}
private void setContactsBackupEnabled(final boolean enabled) {
arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREFERENCE_CONTACTS_BACKUP_ENABLED, enabled);
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// use grey as fallback for elements where custom theming is not available
if (themeUtils.themingEnabled(getContext())) {
getContext().getTheme().applyStyle(R.style.FallbackThemingTheme, true);
}
binding = BackupFragmentBinding.inflate(inflater, container, false);
View view = binding.getRoot();
setHasOptionsMenu(true);
if (getArguments() != null) {
showSidebar = getArguments().getBoolean(ARG_SHOW_SIDEBAR);
}
showCalendarBackup = requireContext().getResources().getBoolean(R.bool.show_calendar_backup);
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
user = contactsPreferenceActivity.getUser().orElseThrow(RuntimeException::new);
ActionBar actionBar = contactsPreferenceActivity != null ? contactsPreferenceActivity.getSupportActionBar() : null;
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
viewThemeUtils.files.themeActionBar(requireContext(), actionBar,
showCalendarBackup ? R.string.backup_title : R.string.contact_backup_title);
}
viewThemeUtils.androidx.colorSwitchCompat(binding.contacts);
viewThemeUtils.androidx.colorSwitchCompat(binding.calendar);
viewThemeUtils.androidx.colorSwitchCompat(binding.dailyBackup);
binding.dailyBackup.setChecked(arbitraryDataProvider.getBooleanValue(user,
PREFERENCE_CONTACTS_AUTOMATIC_BACKUP));
binding.contacts.setChecked(isContactsBackupEnabled() && checkContactBackupPermission());
binding.calendar.setChecked(isCalendarBackupEnabled() && checkCalendarBackupPermission(getContext()));
binding.calendar.setVisibility(showCalendarBackup ? View.VISIBLE : View.GONE);
setupCheckListeners();
setBackupNowButtonVisibility();
binding.backupNow.setOnClickListener(v -> backupNow());
binding.contactsDatepicker.setOnClickListener(v -> openCleanDate());
// display last backup
Long lastBackupTimestamp = arbitraryDataProvider.getLongValue(user, PREFERENCE_CONTACTS_LAST_BACKUP);
if (lastBackupTimestamp == -1) {
binding.lastBackupWithDate.setVisibility(View.GONE);
} else {
binding.lastBackupWithDate.setText(
String.format(getString(R.string.last_backup),
DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp)));
}
if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) {
if (savedInstanceState.getInt(KEY_CALENDAR_YEAR, -1) != -1 &&
savedInstanceState.getInt(KEY_CALENDAR_MONTH, -1) != -1 &&
savedInstanceState.getInt(KEY_CALENDAR_DAY, -1) != -1) {
selectedDate = new Date(savedInstanceState.getInt(KEY_CALENDAR_YEAR),
savedInstanceState.getInt(KEY_CALENDAR_MONTH), savedInstanceState.getInt(KEY_CALENDAR_DAY));
}
calendarPickerOpen = true;
}
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.backupNow);
viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker);
viewThemeUtils.platform.colorTextView(binding.dataToBackUpTitle);
viewThemeUtils.platform.colorTextView(binding.backupSettingsTitle);
return view;
}
private void setupCheckListeners() {
dailyBackupCheckedChangeListener = (buttonView, isChecked) -> {
if (checkAndAskForContactsReadPermission()) {
setAutomaticBackup(isChecked);
}
};
binding.dailyBackup.setOnCheckedChangeListener(dailyBackupCheckedChangeListener);
contactsCheckedListener = (buttonView, isChecked) -> {
if (isChecked) {
if (checkAndAskForContactsReadPermission()) {
setContactsBackupEnabled(true);
}
} else {
setContactsBackupEnabled(false);
}
setBackupNowButtonVisibility();
setAutomaticBackup(binding.dailyBackup.isChecked());
};
binding.contacts.setOnCheckedChangeListener(contactsCheckedListener);
calendarCheckedListener = (buttonView, isChecked) -> {
if (isChecked) {
if (checkAndAskForCalendarReadPermission()) {
setCalendarBackupEnabled(true);
}
} else {
setCalendarBackupEnabled(false);
}
setBackupNowButtonVisibility();
setAutomaticBackup(binding.dailyBackup.isChecked());
};
binding.calendar.setOnCheckedChangeListener(calendarCheckedListener);
}
private void setBackupNowButtonVisibility() {
if (binding.contacts.isChecked() || binding.calendar.isChecked()) {
binding.backupNow.setVisibility(View.VISIBLE);
} else {
binding.backupNow.setVisibility(View.INVISIBLE);
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
if (calendarPickerOpen) {
if (selectedDate != null) {
openDate(selectedDate);
} else {
openDate(null);
}
}
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
if (contactsPreferenceActivity != null) {
String backupFolderPath = getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
refreshBackupFolder(backupFolderPath,
contactsPreferenceActivity.getApplicationContext(),
contactsPreferenceActivity.getStorageManager());
}
}
private void refreshBackupFolder(final String backupFolderPath,
final Context context,
final FileDataStorageManager storageManager) {
AsyncTask<String, Integer, Boolean> task = new AsyncTask<String, Integer, Boolean>() {
@Override
protected Boolean doInBackground(String... path) {
OCFile folder = storageManager.getFileByPath(path[0]);
if (folder != null) {
RefreshFolderOperation operation = new RefreshFolderOperation(folder, System.currentTimeMillis(),
false, false, storageManager, user, context);
RemoteOperationResult result = operation.execute(user, context);
return result.isSuccess();
} else {
return Boolean.FALSE;
}
}
@Override
protected void onPostExecute(Boolean result) {
if (result && binding != null) {
OCFile backupFolder = storageManager.getFileByPath(backupFolderPath);
List<OCFile> backupFiles = storageManager
.getFolderContent(backupFolder, false);
Collections.sort(backupFiles, new AlphanumComparator<>());
if (backupFiles == null || backupFiles.isEmpty()) {
binding.contactsDatepicker.setVisibility(View.INVISIBLE);
} else {
binding.contactsDatepicker.setVisibility(View.VISIBLE);
}
}
}
};
task.execute(backupFolderPath);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
boolean retval;
switch (item.getItemId()) {
case android.R.id.home:
if (showSidebar) {
if (contactsPreferenceActivity.isDrawerOpen()) {
contactsPreferenceActivity.closeDrawer();
} else {
contactsPreferenceActivity.openDrawer();
}
} else if (getActivity() != null) {
getActivity().finish();
} else {
Intent settingsIntent = new Intent(getContext(), SettingsActivity.class);
settingsIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(settingsIntent);
}
retval = true;
break;
default:
retval = super.onOptionsItemSelected(item);
break;
}
return retval;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC) {
for (int index = 0; index < permissions.length; index++) {
if (Manifest.permission.READ_CONTACTS.equalsIgnoreCase(permissions[index])) {
if (grantResults[index] >= 0) {
// if approved, exit for loop
setContactsBackupEnabled(true);
break;
}
// if not accepted, disable again
binding.contacts.setOnCheckedChangeListener(null);
binding.contacts.setChecked(false);
binding.contacts.setOnCheckedChangeListener(contactsCheckedListener);
}
}
}
if (requestCode == PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC) {
boolean readGranted = false;
boolean writeGranted = false;
for (int index = 0; index < permissions.length; index++) {
if (Manifest.permission.WRITE_CALENDAR.equalsIgnoreCase(permissions[index]) && grantResults[index] >= 0) {
writeGranted = true;
} else if (Manifest.permission.READ_CALENDAR.equalsIgnoreCase(permissions[index]) && grantResults[index] >= 0) {
readGranted = true;
}
}
if (!readGranted || !writeGranted) {
// if not accepted, disable again
binding.calendar.setOnCheckedChangeListener(null);
binding.calendar.setChecked(false);
binding.calendar.setOnCheckedChangeListener(calendarCheckedListener);
} else {
setCalendarBackupEnabled(true);
}
}
setBackupNowButtonVisibility();
setAutomaticBackup(binding.dailyBackup.isChecked());
}
public void backupNow() {
if (isContactsBackupEnabled() && checkContactBackupPermission()) {
startContactsBackupJob();
}
if (showCalendarBackup && isCalendarBackupEnabled() && checkCalendarBackupPermission(requireContext())) {
startCalendarBackupJob();
}
DisplayUtils.showSnackMessage(requireView().findViewById(R.id.contacts_linear_layout),
R.string.contacts_preferences_backup_scheduled);
}
private void startContactsBackupJob() {
ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
if (activity != null) {
Optional<User> optionalUser = activity.getUser();
if (optionalUser.isPresent()) {
backgroundJobManager.startImmediateContactsBackup(optionalUser.get());
}
}
}
private void startCalendarBackupJob() {
ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
if (activity != null) {
Optional<User> optionalUser = activity.getUser();
if (optionalUser.isPresent()) {
backgroundJobManager.startImmediateCalendarBackup(optionalUser.get());
}
}
}
private void setAutomaticBackup(final boolean enabled) {
final ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
if (activity == null) {
return;
}
Optional<User> optionalUser = activity.getUser();
if (!optionalUser.isPresent()) {
return;
}
User user = optionalUser.get();
if (enabled) {
if (isContactsBackupEnabled()) {
Log_OC.d(TAG, "Scheduling contacts backup job");
backgroundJobManager.schedulePeriodicContactsBackup(user);
} else {
Log_OC.d(TAG, "Cancelling contacts backup job");
backgroundJobManager.cancelPeriodicContactsBackup(user);
}
if (isCalendarBackupEnabled()) {
Log_OC.d(TAG, "Scheduling calendar backup job");
backgroundJobManager.schedulePeriodicCalendarBackup(user);
} else {
Log_OC.d(TAG, "Cancelling calendar backup job");
backgroundJobManager.cancelPeriodicCalendarBackup(user);
}
} else {
Log_OC.d(TAG, "Cancelling all backup jobs");
backgroundJobManager.cancelPeriodicContactsBackup(user);
backgroundJobManager.cancelPeriodicCalendarBackup(user);
}
arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
String.valueOf(enabled));
}
private boolean checkAndAskForContactsReadPermission() {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
// check permissions
if (PermissionUtil.checkSelfPermission(contactsPreferenceActivity, Manifest.permission.READ_CONTACTS)) {
return true;
} else {
// No explanation needed, request the permission.
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC);
return false;
}
}
private boolean checkAndAskForCalendarReadPermission() {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
// check permissions
if (checkCalendarBackupPermission(contactsPreferenceActivity)) {
return true;
} else {
// No explanation needed, request the permission.
requestPermissions(new String[]{Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR},
PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC);
return false;
}
}
private boolean checkCalendarBackupPermission(final Context context) {
return PermissionUtil.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) && PermissionUtil.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR);
}
private boolean checkContactBackupPermission() {
return PermissionUtil.checkSelfPermission(getContext(), Manifest.permission.READ_CONTACTS);
}
public void openCleanDate() {
if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) {
openDate(null);
}
}
public void openDate(@Nullable Date savedDate) {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
if (contactsPreferenceActivity == null) {
Toast.makeText(getContext(), getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show();
return;
}
String contactsBackupFolderString =
getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
String calendarBackupFolderString =
getResources().getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR;
FileDataStorageManager storageManager = contactsPreferenceActivity.getStorageManager();
OCFile contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString);
OCFile calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString);
List<OCFile> backupFiles = storageManager.getFolderContent(contactsBackupFolder, false);
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false));
Collections.sort(backupFiles, (o1, o2) -> {
return Long.compare(o1.getModificationTimestamp(), o2.getModificationTimestamp());
});
Calendar cal = Calendar.getInstance();
int year;
int month;
int day;
if (savedDate == null) {
year = cal.get(Calendar.YEAR);
month = cal.get(Calendar.MONTH) + 1;
day = cal.get(Calendar.DAY_OF_MONTH);
} else {
year = savedDate.getYear();
month = savedDate.getMonth();
day = savedDate.getDay();
}
if (backupFiles.size() > 0 && backupFiles.get(backupFiles.size() - 1) != null) {
datePickerDialog = new DatePickerDialog(contactsPreferenceActivity, this, year, month, day);
datePickerDialog.getDatePicker().setMaxDate(backupFiles.get(backupFiles.size() - 1)
.getModificationTimestamp());
datePickerDialog.getDatePicker().setMinDate(backupFiles.get(0).getModificationTimestamp());
datePickerDialog.setOnDismissListener(dialog -> selectedDate = null);
datePickerDialog.setTitle("");
datePickerDialog.show();
viewThemeUtils.platform.colorTextButtons(datePickerDialog.getButton(DatePickerDialog.BUTTON_NEGATIVE),
datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE));
// set background to transparent
datePickerDialog.getButton(DatePickerDialog.BUTTON_NEGATIVE).setBackgroundColor(0x00000000);
datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE).setBackgroundColor(0x00000000);
} else {
DisplayUtils.showSnackMessage(getView().findViewById(R.id.contacts_linear_layout),
R.string.contacts_preferences_something_strange_happened);
}
}
@Override
public void onDestroyView() {
binding = null;
super.onDestroyView();
}
@Override
public void onStop() {
super.onStop();
if (datePickerDialog != null) {
datePickerDialog.dismiss();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (datePickerDialog != null) {
outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, datePickerDialog.isShowing());
if (datePickerDialog.isShowing()) {
outState.putInt(KEY_CALENDAR_DAY, datePickerDialog.getDatePicker().getDayOfMonth());
outState.putInt(KEY_CALENDAR_MONTH, datePickerDialog.getDatePicker().getMonth());
outState.putInt(KEY_CALENDAR_YEAR, datePickerDialog.getDatePicker().getYear());
}
}
}
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
if (contactsPreferenceActivity == null) {
Toast.makeText(getContext(), getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show();
return;
}
selectedDate = new Date(year, month, dayOfMonth);
String contactsBackupFolderString =
getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
String calendarBackupFolderString =
getResources().getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR;
FileDataStorageManager storageManager = contactsPreferenceActivity.getStorageManager();
OCFile contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString);
OCFile calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString);
List<OCFile> backupFiles = storageManager.getFolderContent(contactsBackupFolder, false);
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false));
// find file with modification with date and time between 00:00 and 23:59
// if more than one file exists, take oldest
Calendar date = Calendar.getInstance();
date.set(year, month, dayOfMonth);
// start
date.set(Calendar.HOUR, 0);
date.set(Calendar.MINUTE, 0);
date.set(Calendar.SECOND, 1);
date.set(Calendar.MILLISECOND, 0);
date.set(Calendar.AM_PM, Calendar.AM);
long start = date.getTimeInMillis();
// end
date.set(Calendar.HOUR, 23);
date.set(Calendar.MINUTE, 59);
date.set(Calendar.SECOND, 59);
long end = date.getTimeInMillis();
OCFile contactsBackupToRestore = null;
List<OCFile> calendarBackupsToRestore = new ArrayList<>();
for (OCFile file : backupFiles) {
if (start < file.getModificationTimestamp() && end > file.getModificationTimestamp()) {
// contact
if (MimeTypeUtil.isVCard(file)) {
if (contactsBackupToRestore == null) {
contactsBackupToRestore = file;
} else if (contactsBackupToRestore.getModificationTimestamp() < file.getModificationTimestamp()) {
contactsBackupToRestore = file;
}
}
// calendars
if (showCalendarBackup && MimeTypeUtil.isCalendar(file)) {
calendarBackupsToRestore.add(file);
}
}
}
List<OCFile> backupToRestore = new ArrayList<>();
if (contactsBackupToRestore != null) {
backupToRestore.add(contactsBackupToRestore);
}
backupToRestore.addAll(calendarBackupsToRestore);
if (backupToRestore.isEmpty()) {
DisplayUtils.showSnackMessage(getView().findViewById(R.id.contacts_linear_layout),
R.string.contacts_preferences_no_file_found);
} else {
final User user = contactsPreferenceActivity.getUser().orElseThrow(RuntimeException::new);
OCFile[] files = new OCFile[backupToRestore.size()];
Fragment contactListFragment = BackupListFragment.newInstance(backupToRestore.toArray(files), user);
contactsPreferenceActivity.getSupportFragmentManager().
beginTransaction()
.replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG)
.addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST)
.commit();
}
}
}

View file

@ -0,0 +1,726 @@
/*
* Nextcloud Android client application
*
* @author Mario Danic
* @author TSI-mc
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2017 Nextcloud GmbH.
* Copyright (C) 2023 TSI-mc
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.fragment.contactsbackup
import android.Manifest
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.app.DatePickerDialog.OnDateSetListener
import android.content.Context
import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.DatePicker
import android.widget.Toast
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.jobs.BackgroundJobManager
import com.owncloud.android.R
import com.owncloud.android.databinding.BackupFragmentBinding
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.RefreshFolderOperation
import com.owncloud.android.ui.activity.ContactsPreferenceActivity
import com.owncloud.android.ui.activity.SettingsActivity
import com.owncloud.android.ui.fragment.FileFragment
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.MimeTypeUtil
import com.owncloud.android.utils.PermissionUtil
import com.owncloud.android.utils.PermissionUtil.checkSelfPermission
import com.owncloud.android.utils.theme.ThemeUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import third_parties.daveKoeller.AlphanumComparator
import java.util.Calendar
import java.util.Collections
import java.util.Date
import javax.inject.Inject
@Suppress("TooManyFunctions")
class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
private lateinit var binding: BackupFragmentBinding
@JvmField
@Inject
var backgroundJobManager: BackgroundJobManager? = null
@JvmField
@Inject
var themeUtils: ThemeUtils? = null
@JvmField
@Inject
var arbitraryDataProvider: ArbitraryDataProvider? = null
@JvmField
@Inject
var viewThemeUtils: ViewThemeUtils? = null
private var selectedDate: Date? = null
private var calendarPickerOpen = false
private var datePickerDialog: DatePickerDialog? = null
private var contactsCheckedListener: CompoundButton.OnCheckedChangeListener? = null
private var calendarCheckedListener: CompoundButton.OnCheckedChangeListener? = null
private var user: User? = null
private var showSidebar = true
// flag to check if calendar backup should be shown and backup should be done or not
private var showCalendarBackup = true
private var isCalendarBackupEnabled: Boolean
get() = user?.let { arbitraryDataProvider?.getBooleanValue(it, PREFERENCE_CALENDAR_BACKUP_ENABLED) } ?: false
private set(enabled) {
arbitraryDataProvider!!.storeOrUpdateKeyValue(
user!!.accountName,
PREFERENCE_CALENDAR_BACKUP_ENABLED,
enabled
)
}
private var isContactsBackupEnabled: Boolean
get() = user?.let { arbitraryDataProvider?.getBooleanValue(it, PREFERENCE_CONTACTS_BACKUP_ENABLED) } ?: false
private set(enabled) {
arbitraryDataProvider!!.storeOrUpdateKeyValue(
user!!.accountName,
PREFERENCE_CONTACTS_BACKUP_ENABLED,
enabled
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// use grey as fallback for elements where custom theming is not available
if (themeUtils?.themingEnabled(context) == true) {
requireContext().theme.applyStyle(R.style.FallbackThemingTheme, true)
}
binding = BackupFragmentBinding.inflate(inflater, container, false)
val view: View = binding.root
setHasOptionsMenu(true)
if (arguments != null) {
showSidebar = requireArguments().getBoolean(ARG_SHOW_SIDEBAR)
}
showCalendarBackup = requireContext().resources.getBoolean(R.bool.show_calendar_backup)
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
user = contactsPreferenceActivity?.user?.orElseThrow { RuntimeException() }
setupSwitches(user)
setupCheckListeners()
setBackupNowButtonVisibility()
setOnClickListeners()
contactsPreferenceActivity?.let {
displayLastBackup(it)
applyUserColorToActionBar(it)
}
setupDates(savedInstanceState)
applyUserColor()
return view
}
private fun setupSwitches(user: User?) {
user?.let {
binding.dailyBackup.isChecked = arbitraryDataProvider?.getBooleanValue(
it,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP
) ?: false
}
binding.contacts.isChecked = isContactsBackupEnabled && checkContactBackupPermission()
binding.calendar.isChecked = isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())
binding.calendar.visibility = if (showCalendarBackup) View.VISIBLE else View.GONE
}
private fun setupCheckListeners() {
binding.dailyBackup.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (checkAndAskForContactsReadPermission()) {
setAutomaticBackup(isChecked)
}
}
initContactsCheckedListener()
binding.contacts.setOnCheckedChangeListener(contactsCheckedListener)
initCalendarCheckedListener()
binding.calendar.setOnCheckedChangeListener(calendarCheckedListener)
}
private fun initContactsCheckedListener() {
contactsCheckedListener =
CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) {
if (checkAndAskForContactsReadPermission()) {
isContactsBackupEnabled = true
}
} else {
isContactsBackupEnabled = false
}
setBackupNowButtonVisibility()
setAutomaticBackup(binding.dailyBackup.isChecked)
}
}
private fun initCalendarCheckedListener() {
calendarCheckedListener =
CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) {
if (checkAndAskForCalendarReadPermission()) {
isCalendarBackupEnabled = true
}
} else {
isCalendarBackupEnabled = false
}
setBackupNowButtonVisibility()
setAutomaticBackup(binding.dailyBackup.isChecked)
}
}
private fun setBackupNowButtonVisibility() {
binding.backupNow.visibility =
if (binding.contacts.isChecked || binding.calendar.isChecked) View.VISIBLE else View.INVISIBLE
}
private fun setOnClickListeners() {
binding.backupNow.setOnClickListener { backupNow() }
binding.contactsDatepicker.setOnClickListener { openCleanDate() }
}
private fun displayLastBackup(contactsPreferenceActivity: ContactsPreferenceActivity) {
val lastBackupTimestamp = user?.let {
arbitraryDataProvider?.getLongValue(
it,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP
)
} ?: -1L
if (lastBackupTimestamp == -1L) {
binding.lastBackupWithDate.visibility = View.GONE
} else {
binding.lastBackupWithDate.text = String.format(
getString(R.string.last_backup),
DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp)
)
}
}
private fun applyUserColorToActionBar(contactsPreferenceActivity: ContactsPreferenceActivity) {
val actionBar = contactsPreferenceActivity.supportActionBar
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true)
viewThemeUtils?.files?.themeActionBar(
requireContext(),
actionBar,
if (showCalendarBackup) R.string.backup_title else R.string.contact_backup_title
)
}
}
private fun setupDates(savedInstanceState: Bundle?) {
if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) {
if (savedInstanceState.getInt(KEY_CALENDAR_YEAR, -1) != -1 && savedInstanceState.getInt(
KEY_CALENDAR_MONTH,
-1
) != -1 && savedInstanceState.getInt(
KEY_CALENDAR_DAY, -1
) != -1
) {
val cal = Calendar.getInstance()
cal[Calendar.YEAR] = savedInstanceState.getInt(KEY_CALENDAR_YEAR)
cal[Calendar.MONTH] = savedInstanceState.getInt(KEY_CALENDAR_MONTH)
cal[Calendar.DAY_OF_MONTH] = savedInstanceState.getInt(KEY_CALENDAR_DAY)
selectedDate = cal.time
}
calendarPickerOpen = true
}
}
private fun applyUserColor() {
viewThemeUtils?.androidx?.colorSwitchCompat(binding.contacts)
viewThemeUtils?.androidx?.colorSwitchCompat(binding.calendar)
viewThemeUtils?.androidx?.colorSwitchCompat(binding.dailyBackup)
viewThemeUtils?.material?.colorMaterialButtonPrimaryFilled(binding.backupNow)
viewThemeUtils?.material?.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker)
viewThemeUtils?.platform?.colorTextView(binding.dataToBackUpTitle)
viewThemeUtils?.platform?.colorTextView(binding.backupSettingsTitle)
}
override fun onResume() {
super.onResume()
if (calendarPickerOpen) {
if (selectedDate != null) {
openDate(selectedDate)
} else {
openDate(null)
}
}
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
if (contactsPreferenceActivity != null) {
val backupFolderPath = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
refreshBackupFolder(
backupFolderPath,
contactsPreferenceActivity.applicationContext,
contactsPreferenceActivity.storageManager
)
}
}
private fun refreshBackupFolder(
backupFolderPath: String,
context: Context,
storageManager: FileDataStorageManager
) {
val task: AsyncTask<String, Int, Boolean> =
@SuppressLint("StaticFieldLeak")
object : AsyncTask<String, Int, Boolean>() {
@Deprecated("Deprecated in Java")
override fun doInBackground(vararg path: String): Boolean {
val folder = storageManager.getFileByPath(path[0])
return if (folder != null) {
val operation = RefreshFolderOperation(
folder,
System.currentTimeMillis(),
false,
false,
storageManager,
user,
context
)
val result = operation.execute(user, context)
result.isSuccess
} else {
java.lang.Boolean.FALSE
}
}
@Deprecated("Deprecated in Java")
override fun onPostExecute(result: Boolean) {
if (result) {
val backupFolder = storageManager.getFileByPath(backupFolderPath)
val backupFiles = storageManager
.getFolderContent(backupFolder, false)
Collections.sort(backupFiles, AlphanumComparator())
if (backupFiles == null || backupFiles.isEmpty()) {
binding.contactsDatepicker.visibility = View.INVISIBLE
} else {
binding.contactsDatepicker.visibility = View.VISIBLE
}
}
}
}
task.execute(backupFolderPath)
}
@Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
val retval: Boolean
when (item.itemId) {
android.R.id.home -> {
if (showSidebar) {
if (contactsPreferenceActivity!!.isDrawerOpen) {
contactsPreferenceActivity.closeDrawer()
} else {
contactsPreferenceActivity.openDrawer()
}
} else if (activity != null) {
requireActivity().finish()
} else {
val settingsIntent = Intent(context, SettingsActivity::class.java)
settingsIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(settingsIntent)
}
retval = true
}
else -> retval = super.onOptionsItemSelected(item)
}
return retval
}
@Deprecated("Deprecated in Java")
@Suppress("NestedBlockDepth")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC) {
for (index in permissions.indices) {
if (Manifest.permission.READ_CONTACTS.equals(permissions[index], ignoreCase = true)) {
if (grantResults[index] >= 0) {
// if approved, exit for loop
isContactsBackupEnabled = true
break
}
// if not accepted, disable again
binding.contacts.setOnCheckedChangeListener(null)
binding.contacts.isChecked = false
binding.contacts.setOnCheckedChangeListener(contactsCheckedListener)
}
}
}
if (requestCode == PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC) {
var readGranted = false
var writeGranted = false
for (index in permissions.indices) {
if (Manifest.permission.WRITE_CALENDAR.equals(
permissions[index],
ignoreCase = true
) && grantResults[index] >= 0
) {
writeGranted = true
} else if (Manifest.permission.READ_CALENDAR.equals(
permissions[index],
ignoreCase = true
) && grantResults[index] >= 0
) {
readGranted = true
}
}
if (!readGranted || !writeGranted) {
// if not accepted, disable again
binding.calendar.setOnCheckedChangeListener(null)
binding.calendar.isChecked = false
binding.calendar.setOnCheckedChangeListener(calendarCheckedListener)
} else {
isCalendarBackupEnabled = true
}
}
setBackupNowButtonVisibility()
setAutomaticBackup(binding.dailyBackup.isChecked)
}
private fun backupNow() {
if (isContactsBackupEnabled && checkContactBackupPermission()) {
startContactsBackupJob()
}
if (showCalendarBackup && isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())) {
startCalendarBackupJob()
}
DisplayUtils.showSnackMessage(
requireView().findViewById<View>(R.id.contacts_linear_layout),
R.string.contacts_preferences_backup_scheduled
)
}
private fun startContactsBackupJob() {
val activity = activity as ContactsPreferenceActivity?
if (activity != null) {
val optionalUser = activity.user
if (optionalUser.isPresent) {
backgroundJobManager!!.startImmediateContactsBackup(optionalUser.get())
}
}
}
private fun startCalendarBackupJob() {
val activity = activity as ContactsPreferenceActivity?
if (activity != null) {
val optionalUser = activity.user
if (optionalUser.isPresent) {
backgroundJobManager!!.startImmediateCalendarBackup(optionalUser.get())
}
}
}
private fun setAutomaticBackup(enabled: Boolean) {
val activity = activity as ContactsPreferenceActivity? ?: return
val optionalUser = activity.user
if (!optionalUser.isPresent) {
return
}
val user = optionalUser.get()
if (enabled) {
if (isContactsBackupEnabled) {
Log_OC.d(TAG, "Scheduling contacts backup job")
backgroundJobManager?.schedulePeriodicContactsBackup(user)
} else {
Log_OC.d(TAG, "Cancelling contacts backup job")
backgroundJobManager?.cancelPeriodicContactsBackup(user)
}
if (isCalendarBackupEnabled) {
Log_OC.d(TAG, "Scheduling calendar backup job")
backgroundJobManager?.schedulePeriodicCalendarBackup(user)
} else {
Log_OC.d(TAG, "Cancelling calendar backup job")
backgroundJobManager?.cancelPeriodicCalendarBackup(user)
}
} else {
Log_OC.d(TAG, "Cancelling all backup jobs")
backgroundJobManager?.cancelPeriodicContactsBackup(user)
backgroundJobManager?.cancelPeriodicCalendarBackup(user)
}
arbitraryDataProvider?.storeOrUpdateKeyValue(
user.accountName,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
enabled.toString()
)
}
private fun checkAndAskForContactsReadPermission(): Boolean {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
// check permissions
return if (checkSelfPermission(contactsPreferenceActivity!!, Manifest.permission.READ_CONTACTS)) {
true
} else {
// No explanation needed, request the permission.
requestPermissions(
arrayOf(Manifest.permission.READ_CONTACTS),
PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC
)
false
}
}
private fun checkAndAskForCalendarReadPermission(): Boolean {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
// check permissions
return if (contactsPreferenceActivity?.let { checkCalendarBackupPermission(it) } == true) {
true
} else {
// No explanation needed, request the permission.
requestPermissions(
arrayOf(
Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR
),
PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC
)
false
}
}
private fun checkCalendarBackupPermission(context: Context): Boolean {
return checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) && checkSelfPermission(
context, Manifest.permission.WRITE_CALENDAR
)
}
private fun checkContactBackupPermission(): Boolean {
return checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS)
}
private fun openCleanDate() {
if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) {
openDate(null)
}
}
private fun openDate(savedDate: Date?) {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
if (contactsPreferenceActivity == null) {
Toast.makeText(context, getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show()
return
}
val contactsBackupFolderString = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
val calendarBackupFolderString = resources.getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
val storageManager = contactsPreferenceActivity.storageManager
val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString)
val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString)
val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
backupFiles.sortWith { o1: OCFile?, o2: OCFile? ->
if (o1 != null && o2 != null) {
o1.modificationTimestamp.compareTo(o2.modificationTimestamp)
} else {
-1
}
}
val cal = Calendar.getInstance()
val year: Int
val month: Int
val day: Int
if (savedDate == null) {
year = cal[Calendar.YEAR]
month = cal[Calendar.MONTH] + 1
day = cal[Calendar.DAY_OF_MONTH]
} else {
year = savedDate.year
month = savedDate.month
day = savedDate.day
}
if (backupFiles.size > 0 && backupFiles[backupFiles.size - 1] != null) {
datePickerDialog = DatePickerDialog(contactsPreferenceActivity, this, year, month, day)
datePickerDialog?.datePicker?.maxDate = backupFiles[backupFiles.size - 1]!!
.modificationTimestamp
datePickerDialog?.datePicker?.minDate = backupFiles[0]!!.modificationTimestamp
datePickerDialog?.setOnDismissListener { selectedDate = null }
datePickerDialog?.setTitle("")
datePickerDialog?.show()
viewThemeUtils?.platform?.colorTextButtons(
datePickerDialog!!.getButton(DatePickerDialog.BUTTON_NEGATIVE),
datePickerDialog!!.getButton(DatePickerDialog.BUTTON_POSITIVE)
)
// set background to transparent
datePickerDialog?.getButton(DatePickerDialog.BUTTON_NEGATIVE)?.setBackgroundColor(0x00000000)
datePickerDialog?.getButton(DatePickerDialog.BUTTON_POSITIVE)?.setBackgroundColor(0x00000000)
} else {
DisplayUtils.showSnackMessage(
requireView().findViewById<View>(R.id.contacts_linear_layout),
R.string.contacts_preferences_something_strange_happened
)
}
}
override fun onStop() {
super.onStop()
datePickerDialog?.dismiss()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
datePickerDialog?.let {
outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, it.isShowing)
if (it.isShowing) {
outState.putInt(KEY_CALENDAR_DAY, it.datePicker.dayOfMonth)
outState.putInt(KEY_CALENDAR_MONTH, it.datePicker.month)
outState.putInt(KEY_CALENDAR_YEAR, it.datePicker.year)
}
}
}
@Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "ComplexMethod", "LongMethod", "MagicNumber")
override fun onDateSet(view: DatePicker, year: Int, month: Int, dayOfMonth: Int) {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
if (contactsPreferenceActivity == null) {
Toast.makeText(context, getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show()
return
}
selectedDate = Date(year, month, dayOfMonth)
val contactsBackupFolderString = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
val calendarBackupFolderString = resources.getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
val storageManager = contactsPreferenceActivity.storageManager
val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString)
val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString)
val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
// find file with modification with date and time between 00:00 and 23:59
// if more than one file exists, take oldest
val date = Calendar.getInstance()
date[year, month] = dayOfMonth
// start
date[Calendar.HOUR] = 0
date[Calendar.MINUTE] = 0
date[Calendar.SECOND] = 1
date[Calendar.MILLISECOND] = 0
date[Calendar.AM_PM] = Calendar.AM
val start = date.timeInMillis
// end
date[Calendar.HOUR] = 23
date[Calendar.MINUTE] = 59
date[Calendar.SECOND] = 59
val end = date.timeInMillis
var contactsBackupToRestore: OCFile? = null
val calendarBackupsToRestore: MutableList<OCFile> = ArrayList()
for (file in backupFiles) {
if (start < file.modificationTimestamp && end > file.modificationTimestamp) {
// contact
if (MimeTypeUtil.isVCard(file)) {
if (contactsBackupToRestore == null) {
contactsBackupToRestore = file
} else if (contactsBackupToRestore.modificationTimestamp < file.modificationTimestamp) {
contactsBackupToRestore = file
}
}
// calendars
if (showCalendarBackup && MimeTypeUtil.isCalendar(file)) {
calendarBackupsToRestore.add(file)
}
}
}
val backupToRestore: MutableList<OCFile> = ArrayList()
if (contactsBackupToRestore != null) {
backupToRestore.add(contactsBackupToRestore)
}
backupToRestore.addAll(calendarBackupsToRestore)
if (backupToRestore.isEmpty()) {
DisplayUtils.showSnackMessage(
requireView().findViewById<View>(R.id.contacts_linear_layout),
R.string.contacts_preferences_no_file_found
)
} else {
val user = contactsPreferenceActivity.user.orElseThrow { RuntimeException() }
val files: Array<OCFile?> = arrayOfNulls(backupToRestore.size)
val contactListFragment = BackupListFragment.newInstance(files, user)
contactsPreferenceActivity.supportFragmentManager.beginTransaction()
.replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG)
.addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST)
.commit()
}
}
companion object {
val TAG = BackupFragment::class.java.simpleName
private const val ARG_SHOW_SIDEBAR = "SHOW_SIDEBAR"
private const val KEY_CALENDAR_PICKER_OPEN = "IS_CALENDAR_PICKER_OPEN"
private const val KEY_CALENDAR_DAY = "CALENDAR_DAY"
private const val KEY_CALENDAR_MONTH = "CALENDAR_MONTH"
private const val KEY_CALENDAR_YEAR = "CALENDAR_YEAR"
const val PREFERENCE_CONTACTS_BACKUP_ENABLED = "PREFERENCE_CONTACTS_BACKUP_ENABLED"
const val PREFERENCE_CALENDAR_BACKUP_ENABLED = "PREFERENCE_CALENDAR_BACKUP_ENABLED"
@JvmStatic
fun create(showSidebar: Boolean): BackupFragment {
val fragment = BackupFragment()
val bundle = Bundle()
bundle.putBoolean(ARG_SHOW_SIDEBAR, showSidebar)
fragment.arguments = bundle
return fragment
}
}
}

View file

@ -17,8 +17,8 @@
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/contacts_linear_layout"
android:layout_width="match_parent"
@ -30,14 +30,14 @@
android:layout_margin="@dimen/standard_margin"
android:orientation="vertical">
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/data_to_back_up_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/data_to_back_up"
android:textStyle="bold" />
<androidx.appcompat.widget.SwitchCompat
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/contacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -46,22 +46,21 @@
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size" />
<androidx.appcompat.widget.SwitchCompat
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/calendar"
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size"
/>
android:textSize="@dimen/two_line_primary_text_size" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/backup_settings_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -73,7 +72,7 @@
android:layout_width="match_parent"
android:layout_height="48dp">
<androidx.appcompat.widget.SwitchCompat
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/daily_backup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -89,14 +88,14 @@
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/daily_backup"
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size" />
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/last_backup_with_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -113,32 +112,27 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/standard_margin"
android:gravity="center"
android:orientation="horizontal"
android:weightSum="1.0">
android:gravity="end"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/contacts_datepicker"
style="@style/OutlinedButton"
android:layout_width="0dp"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="@dimen/backup_button_width"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:minHeight="@dimen/minimum_size_for_touchable_area"
android:text="@string/restore_backup"
android:visibility="invisible"
app:cornerRadius="@dimen/button_corner_radius"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/backup_now"
android:layout_width="0dp"
android:layout_width="@dimen/backup_button_width"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/standard_half_margin"
android:layout_weight="0.5"
android:minHeight="@dimen/minimum_size_for_touchable_area"
android:text="@string/contacts_backup_button"
android:theme="@style/Button.Primary"
app:cornerRadius="@dimen/button_corner_radius" />
android:theme="@style/Widget.Material3.Button.IconButton.Filled" />
</LinearLayout>

View file

@ -660,6 +660,7 @@
<string name="share_no_password_title">Đặt mật khẩu</string>
<string name="share_password_title">Mật khẩu được bảo vệ</string>
<string name="share_permission_can_edit">Có thể chỉnh sửa</string>
<string name="share_permission_file_drop">Thả file</string>
<string name="share_permission_view_only">Chỉ xem</string>
<string name="share_permissions">Quyền kho</string>
<string name="share_remote_clarification">%1$s (từ xa)</string>

View file

@ -133,6 +133,8 @@
<dimen name="bottom_sheet_text_size">16sp</dimen>
<dimen name="permission_dialog_text_size">18sp</dimen>
<dimen name="button_corner_radius">24dp</dimen>
<dimen name="backup_button_width">160dp</dimen>
<integer name="media_grid_width">4</integer>
<dimen name="account_action_button_margin">12dp</dimen>
<dimen name="account_action_button_height">50dp</dimen>

View file

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 80 warnings</span>
<span class="mdl-layout-title">Lint Report: 79 warnings</span>