diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListAdapter.kt index 8e2d87edc295..431c6fd83abc 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListAdapter.kt @@ -8,6 +8,7 @@ */ package com.owncloud.android.ui.fragment.contactsbackup +import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources @@ -36,6 +37,7 @@ import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.ui.TextDrawable import com.owncloud.android.ui.fragment.contactsbackup.BackupListFragment.getDisplayName import com.owncloud.android.utils.BitmapUtils +import com.owncloud.android.utils.PermissionUtil import com.owncloud.android.utils.theme.ViewThemeUtils import ezvcard.VCard import ezvcard.property.Photo @@ -68,7 +70,17 @@ class BackupListAdapter( init { shouldShowHeadersForEmptySections(false) shouldShowFooters(false) - cachedAndroidCalendars = AndroidCalendar.loadAll(context.contentResolver) + cachedAndroidCalendars = loadCalendarsIfPermitted() + } + + private fun loadCalendarsIfPermitted(): List { + if (!PermissionUtil.checkSelfPermission(context, Manifest.permission.READ_CALENDAR)) { + Log_OC.d(BackupListFragment.TAG, "READ_CALENDAR permission is not granted, requesting it") + backupListFragment.checkAndAskForCalendarReadPermission() + return emptyList() + } + + return AndroidCalendar.loadAll(context) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionedViewHolder = when (viewType) { @@ -126,6 +138,12 @@ class BackupListAdapter( notifyItemInserted(calendarFiles.lastIndex) } + @SuppressLint("NotifyDataSetChanged") + fun reloadCalendars() { + cachedAndroidCalendars = loadCalendarsIfPermitted() + notifyDataSetChanged() + } + @SuppressLint("NotifyDataSetChanged") fun replaceVcards(vCards: List) { contacts.clear() diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java index 20148300aa88..fd1b2ee25297 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java @@ -58,6 +58,8 @@ import javax.inject.Inject; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.recyclerview.widget.LinearLayoutManager; @@ -408,58 +410,72 @@ private void closeFragment() { } } + // region permission check result listener + private final ActivityResultLauncher contactsWritePermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + importContacts(selectedAccount); + } else { + showPermissionErrorMessage(); + } + }); + + private final ActivityResultLauncher calendarReadPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + listAdapter.reloadCalendars(); + } + }); + + private final ActivityResultLauncher calendarWritePermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + importCalendar(); + } else { + showPermissionErrorMessage(); + } + }); + // endregion + + // region permission checks private boolean checkAndAskForContactsWritePermission() { - // check permissions - if (!PermissionUtil.checkSelfPermission(getContext(), Manifest.permission.WRITE_CONTACTS)) { - requestPermissions(new String[]{Manifest.permission.WRITE_CONTACTS}, - PermissionUtil.PERMISSIONS_WRITE_CONTACTS); + final var context = getContext(); + if (context == null) { return false; - } else { - return true; } - } - private boolean checkAndAskForCalendarWritePermission() { - // check permissions - if (!PermissionUtil.checkSelfPermission(getContext(), Manifest.permission.WRITE_CALENDAR)) { - requestPermissions(new String[]{Manifest.permission.WRITE_CALENDAR}, - PermissionUtil.PERMISSIONS_WRITE_CALENDAR); + if (!PermissionUtil.checkSelfPermission(context, Manifest.permission.WRITE_CONTACTS)) { + contactsWritePermissionLauncher.launch(Manifest.permission.WRITE_CONTACTS); return false; - } else { - return true; } + + return true; } - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - if (requestCode == PermissionUtil.PERMISSIONS_WRITE_CONTACTS) { - for (int index = 0; index < permissions.length; index++) { - if (Manifest.permission.WRITE_CONTACTS.equalsIgnoreCase(permissions[index])) { - if (grantResults[index] >= 0) { - importContacts(selectedAccount); - } else { - showPermissionErrorMessage(); - } - break; - } - } + public void checkAndAskForCalendarReadPermission() { + final var context = getContext(); + if (context == null) { + return; } - if (requestCode == PermissionUtil.PERMISSIONS_WRITE_CALENDAR) { - for (int index = 0; index < permissions.length; index++) { - if (Manifest.permission.WRITE_CALENDAR.equalsIgnoreCase(permissions[index])) { - if (grantResults[index] >= 0) { - importContacts(selectedAccount); - } else { - showPermissionErrorMessage(); - } - break; - } - } + if (!PermissionUtil.checkSelfPermission(context, Manifest.permission.READ_CALENDAR)) { + calendarReadPermissionLauncher.launch(Manifest.permission.READ_CALENDAR); + } + } + + private boolean checkAndAskForCalendarWritePermission() { + final var context = getContext(); + if (context == null) { + return false; + } + + if (!PermissionUtil.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR)) { + calendarWritePermissionLauncher.launch(Manifest.permission.WRITE_CALENDAR); + return false; } + return true; } + // endregion private void showPermissionErrorMessage() { DisplayUtils.showSnackMessage(this, R.string.contactlist_no_permission); diff --git a/app/src/main/java/thirdparties/sufficientlysecure/AndroidCalendar.java b/app/src/main/java/thirdparties/sufficientlysecure/AndroidCalendar.java index c2af23591899..afa6e973802a 100644 --- a/app/src/main/java/thirdparties/sufficientlysecure/AndroidCalendar.java +++ b/app/src/main/java/thirdparties/sufficientlysecure/AndroidCalendar.java @@ -7,8 +7,11 @@ */ package thirdparties.sufficientlysecure; +import android.Manifest; import android.content.ContentProviderClient; import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.provider.CalendarContract.Calendars; @@ -19,6 +22,8 @@ import java.util.ArrayList; import java.util.List; +import androidx.core.content.ContextCompat; + public class AndroidCalendar { private static final String TAG = "ICS_AndroidCalendar"; @@ -48,7 +53,16 @@ public class AndroidCalendar { private static final String[] CAL_ID_COLS = new String[]{Events._ID}; private static final String CAL_ID_WHERE = Events.CALENDAR_ID + "=?"; - // Load all available calendars. + public static List loadAll(Context context) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) + != PackageManager.PERMISSION_GRANTED) { + Log_OC.w(TAG, "Cannot load calendars, READ_CALENDAR permission is not granted"); + return new ArrayList<>(); + } + + return loadAll(context.getContentResolver()); + } + // If an empty list is returned the caller probably needs to enable calendar // read permissions in App Ops/XPrivacy etc. public static List loadAll(ContentResolver resolver) { @@ -112,9 +126,14 @@ private static String getString(Cursor cur, String dbName) { } private static boolean missing(ContentResolver resolver, Uri uri) { - // Determine if a provider is missing + // Determine if a provider is missing or inaccessible. + // acquireContentProviderClient throws SecurityException when the calendar + // permission has not been granted, so treat that as missing too. try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { return provider == null; + } catch (SecurityException e) { + Log_OC.w(TAG, "Calendar provider is not accessible: " + e.getMessage()); + return true; } }