Giter Site home page Giter Site logo

bitfireat / icsx5 Goto Github PK

View Code? Open in Web Editor NEW
139.0 8.0 8.0 1.39 MB

ICSx⁵ is an Android app to subscribe to remote or local iCalendar files (like time tables of your school/university or event files of your sports team).

Home Page: https://icsx5.bitfire.at

License: GNU General Public License v3.0

HTML 17.57% Kotlin 82.20% Shell 0.24%
icalendar webcal

icsx5's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

icsx5's Issues

Rewrite to Jetpack Compose

To experiment with Jetpack Compose (also for DAVx⁵), I'd suggest to rewrite ICSx⁵ to Jetpack Compose as a clean "sample" :)

Depends on #151

URLs do not get encoded

URLs with encoded parameters (eg. %2B (+) or %3D (=)) that are provided with the webcal scheme, get decoded on parsing. For example, if an url like webcal://...?param=abcd%2Befg is introduced, the app converts the URL to webcal://...?param=abcd+efg. Some servers require parameters to be encoded, so this is an issue.

The problem comes from the scheme replacement here:

if (uri.scheme.equals("webcal", true)) {
uri = URI("http", uri.authority, uri.path, uri.query, null)
titleColorModel.url.value = uri.toString()
return null
} else if (uri.scheme.equals("webcals", true)) {
uri = URI("https", uri.authority, uri.path, uri.query, null)
titleColorModel.url.value = uri.toString()
return null
}

It's caused by the way URI constructors parse the contents provided. We should use Uri instead (which by the way reduces the code required, and skips some conversions), and replace the conversion with

uri = uri.buildUpon().scheme("http").build()

Doesn't trust user-installed certificate authorities

Discussed in #41

Originally posted by harryyoud March 18, 2022
Problem: The app uses a custom certificate library which doesn't trust the user's trusted root certificates.

Use case: I have a custom CA for my services at home (radarr, sonarr etc.) which I sync an ICS feed from. These certificates are short-lived (around 36h), so I need to approve the certificate almost every day.

Is there a way to add a trusted certificate authority for this app instead of approving each certificate? I've already added it to the user trusted credentials in Android system settings.

Get calendar color when at subscription

Calendars (Webcal feeds) can contain a color that shall be used for the whole calendar.

Possible representation:

This color could be used as a suggestion when adding a calendar.

  • ResourceInfo: add calendarColor like calendarName
  • AddCalendarValidationFragment.ValidationModel: get calendar color like calendar name
  • AddCalendarValidationFragment: set initial color from calendar color (fall back to lightblue)

Progress bar for synchronization

When doing synchronizations for large datasets, it may take a lot of time. Maybe we should consider adding a proper progress bar when we implement #96

"Couldn't create calendar" when adding a subscription

Somehow I managed to get this exception. The steps were:

  1. I had already a subscription (US holidays).
  2. Add a new subscription (Austrian holidays).
  3. Work through the "Add subscription" dialogs. When finally clicking on the last checkmark, it fails with a Toast and this exception in adb logs:
E/icsx5: Couldn't create calendar
    android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: subscriptions.id (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)
        at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
        at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:940)
        at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790)
        at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:89)
        at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.kt:42)
        at androidx.room.EntityInsertionAdapter.insertAndReturnIdsList(EntityInsertionAdapter.kt:202)
        at at.bitfire.icsdroid.db.dao.SubscriptionsDao_Impl.add(SubscriptionsDao_Impl.java:210)
        at at.bitfire.icsdroid.ui.AddCalendarDetailsFragment$SubscriptionModel$create$1$ids$1.invokeSuspend(AddCalendarDetailsFragment.kt:117)
        at at.bitfire.icsdroid.ui.AddCalendarDetailsFragment$SubscriptionModel$create$1$ids$1.invoke(Unknown Source:8)
        at at.bitfire.icsdroid.ui.AddCalendarDetailsFragment$SubscriptionModel$create$1$ids$1.invoke(Unknown Source:4)
        at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:169)
        at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
        at at.bitfire.icsdroid.ui.AddCalendarDetailsFragment$SubscriptionModel$create$1.invokeSuspend(AddCalendarDetailsFragment.kt:117)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
        at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)

should give proper error message when a synced file is deleted

At the moment the user only sees a message like the following

java.lang.SecurityException: Permission Denial: reading com.android.providers.downloads.DownloadStorageProvider uri content://com.android.providers.downloads.documents/document/msf%3A68 from pid=20876, uid=10151 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

To reproduce either

  • Pick a file, then before adding it,
  • delete the file using filemanager,
  • return to the app and "add" the file.

or

  • Add the file normally, then
  • delete it via filemanager
  • try to sync manually

Couldn't connect to server

Updating the calendars doesn't work regularly. Error message: "Couldn't connec to server."

Seems like the Internet connection is not ready yet when the work manager initiates the synchronization.

Similar to #24

Add tests

  • Add tests for the most important things
  • Integrate tests with CI / Github actions

Can't see dates from Google Calender with only Busy state

I have a Google Calender, with a set of dates. These dates hide all details, its basically only exposing the start time and the end time and the event is called "busy". Sadly, non of these events are displayed and ICSx5 always tells me the calendar don't have any event.

An example event from the ICS looks like this:

BEGIN:VEVENT
DTSTART:20221104T093000Z
DTEND:20221104T100000Z
DTSTAMP:20220307T154542Z
UID:****
ATTENDEE;X-NUM-GUESTS=0:mailto:***
RECURRENCE-ID:20221104T093000Z
SUMMARY:Busy
END:VEVENT
BEGIN:VEVENT
DTSTART:20221028T093000Z
DTEND:20221028T100000Z
DTSTAMP:20220307T154542Z
UID:*******
ATTENDEE;X-NUM-GUESTS=0:mailto:***
RECURRENCE-ID:20221028T093000Z
SUMMARY:Busy
END:VEVENT

It works if I share all the information publicly.

Android 12 compatibility

  • Make ICSx⁵ compatible with Android 12
  • Test with Android 12, especially Android 12-specific things

If there are problems, please track here.

Crash when pressing back button

Steps to reproduce:

  • Preparation: add a subscription, close ICSx⁵
  • Open subscription list
  • Edit a subscription (EditCalendarActivity)
  • Press back button

The app then crashes in the Emulator (Android 12).

I also don't know if we have to add

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
            onBackInvokedDispatcher.registerOnBackInvokedCallback(
                OnBackInvokedDispatcher.PRIORITY_DEFAULT,
            ) { handleOnBackPressed() }
        else
            onBackPressedDispatcher.addCallback { handleOnBackPressed() }

to every (?) activity, looks a bit much. In doubt I'd just ignore this Android 14 back button thing and keep things as simple as possible.

Provide sync menu entry

Currently forcing synchronization is only possible with the swipe-down gesture. However this is

  1. not accessible, and
  2. users may not know this gesture and that it is used for synchronization.

So we should provide an additional way to force synchronization. I think a menu entry (and maybe a Small FAB?) could be appropriate.

Better integration of Worker API

  • Don't set the sync interval over the sync framework, but the Worker API
  • Use CoroutineWorker?
  • Check whether a sync is running necessary? Maybe worker job can be set to "run only once at a time"
  • Return syncResults
  • BootCompleteReceiver still necessary?

Content Provider returning `null` when trying to insert new calendars

Currently synchronization is not working, the system is not allowing to create the calendars in the system. Logs:

I/icsx5: Manual sync, ignoring network condition
I/icsx5: Synchronizing (forceReSync=false,onlyMigrate=false)
D/icsx5: Creating local calendar from subscription #1
E/WM-WorkerWrapper: Work [ id=f2eb9c5e-1f1a-4ebd-95bc-72d284ac7859, tags={ at.bitfire.icsdroid.SyncWorker } ] failed because it threw an exception/error
    java.util.concurrent.ExecutionException: java.lang.Exception: Couldn't create calendar: provider returned null
        at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
        at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
        at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:317)
        at androidx.work.impl.utils.SerialExecutorImpl$Task.run(SerialExecutorImpl.java:96)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
        at java.lang.Thread.run(Thread.java:1012)
     Caused by: java.lang.Exception: Couldn't create calendar: provider returned null
        at at.bitfire.ical4android.AndroidCalendar$Companion.create(AndroidCalendar.kt:63)
        at at.bitfire.icsdroid.SyncWorker.updateLocalCalendars(SyncWorker.kt:186)
        at at.bitfire.icsdroid.SyncWorker.doWork(SyncWorker.kt:112)
        at androidx.work.CoroutineWorker$startWork$1.invokeSuspend(CoroutineWorker.kt:68)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
I/WM-WorkerWrapper: Worker result FAILURE for Work [ id=f2eb9c5e-1f1a-4ebd-95bc-72d284ac7859, tags={ at.bitfire.icsdroid.SyncWorker } ]

Customizable alarms per subscription

  • Support customizable alarms per subscription (for instance: add an alarm 10 min before the event for every event in the subscription)
    • Create UI for setting per subscription: "Add custom alarm" (Edit calendar activity)
    • Save setting per subscription: "Add custom alarm"
    • Actually make this setting work
  • Possibility to disable alarms which come with the .ics
    • Create UI for setting per subscription: "Ignore alarms from file" (Edit calendar activity)
    • Save setting per subscription: "Ignore alarms from file"
    • Actually make this setting work

Process non-recurring events with both UID and RECURRENCE-ID

I have a Google Calender, with a set of dates. These dates hide all details, its basically only exposing the start time and the end time and the event is called "busy". Sadly, non of these events are displayed and ICSx5 always tells me the calendar don't have any event.

An example event from the ICS looks like this:

BEGIN:VEVENT
DTSTART:20221104T093000Z
DTEND:20221104T100000Z
DTSTAMP:20220307T154542Z
UID:****
ATTENDEE;X-NUM-GUESTS=0:mailto:***
RECURRENCE-ID:20221104T093000Z
SUMMARY:Busy
END:VEVENT
BEGIN:VEVENT
DTSTART:20221028T093000Z
DTEND:20221028T100000Z
DTSTAMP:20220307T154542Z
UID:*******
ATTENDEE;X-NUM-GUESTS=0:mailto:***
RECURRENCE-ID:20221028T093000Z
SUMMARY:Busy
END:VEVENT

It works if I share all the information publicly.

Originally posted by @georgkrause in #26

See also https://github.com/bitfireAT/davx5/issues/58

Upgrading to 2.1-beta.2: all subscriptions are "gone" until sync is forced

@ArnyminerZ I have updated ICSx5 to 2.1-beta.2 on my phone, and I noticed that after upgrading, all subscriptions are "gone" until I synchronize. (Because only synchronization migrates the calendar subscripttions to DB subscriptions.)

For me, it's not a problem because I know that I only have to refresh, but there are many people who don't know that. The migration should run automatically. What is your suggestion?

Android 11: move from file URIs to SAF

Since Android 10, file URIs shouldn't/can't access files outside the own application anymore. Since Android 11, file URIs for shared files are possible again, but the better method is SAF and content URIs.

So ICSx⁵ should support SAF instead of file:// URIs.

Move subscriptions to database

If we would move the subscriptions to an app-local database (instead of creating them directly in the Calendar Provider as calendars), we wouldn't have to rely on the Calendar Provider anymore. This would bring some beneftis:

  • For authentication we already use some kind of DB (encrypted shared preferences); a real DB would allow to store authentication and other settings together with the subscription without having to use a limited number of _SYNC columns
  • Settings wouldn't be exposed to other apps.
  • Sometimes accounts are removed by devices (for instance on system upgrades). Then all subscriptions are gone. If we store them ourselves, they would still be there and the calendars can be re-created on sync.
  • Same for backup (#4) – we could then just use autoFullBackup without any extra code.
  • We only need to ask for calendar permissions when there are active subscriptions :)

Steps to implement:

  1. Manage subscriptions locally – Room DB (or maybe shared preferences or whatever seems feasible – but I think other things than Room will again add more "dirtyness").
  2. The sync worker would have to
  • create the account (if it's not already there),
  • create/update calendars from the local subscription DB,
  • sync the calendars.

Subscriptions are added back again by migration

When removing a subscription, it's only removed from the database. When the synchronization runs, the worker sees the calendar on the system, but not the entry in the database, and it considers it as "an old calendar", and migrates it.

This is even worse, since in the conversion from subscription to calendar properties, we don't add NAME:

/**
* Converts this subscription's properties to [android.content.ContentValues] that can be
* passed to the calendar provider in order to create/update the local calendar.
*/
fun toCalendarProperties() = contentValuesOf(
Calendars._ID to id,
Calendars.CALENDAR_DISPLAY_NAME to displayName,
Calendars.CALENDAR_COLOR to color,
Calendars.CALENDAR_ACCESS_LEVEL to Calendars.CAL_ACCESS_READ,
Calendars.SYNC_EVENTS to 1
)

So the system doesn't know the url of the subscription, and just adds them with https://invalid-url as specified here:

id = calendar.id,
url = Uri.parse(calendar.url ?: "https://invalid-url"),
eTag = calendar.eTag,

We should fix the Subscription -> ContentValues conversion with this:

    /**
     * Converts this subscription's properties to [android.content.ContentValues] that can be
     * passed to the calendar provider in order to create/update the local calendar.
     */
    fun toCalendarProperties() = contentValuesOf(
        Calendars._ID to id,
        Calendars.NAME to url.toString(),
        Calendars.CALENDAR_DISPLAY_NAME to displayName,
        Calendars.CALENDAR_COLOR to color,
        Calendars.CALENDAR_ACCESS_LEVEL to Calendars.CAL_ACCESS_READ,
        Calendars.SYNC_EVENTS to 1
    )

Which is fixed here: 3a84525


For migration, I suggest simply removing this process from synchronization, and moving it to the callback on the database creation:

.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
SyncWorker.run(context, onlyMigrate = true)
}
})

Since I think there are really no reasons to run migration again after the database has been created. This also gets rid of ONLY_MIGRATE from the worker, which is a bit specific.

Depends on #133

Remove Locator dependency

When migrating to Android 14, remember to remove the Locator dependency, and use the native localeConfig automatic generation (reference).

Roadmap:

  • Update Android Studio to Giraffe Canary 7 or higher.
  • Update AGP to 8.1.0-alpha07 or higher.
  • Enable generation:
    android {
      androidResources {
        generateLocaleConfig true
      }
    }
  • Set default locale:
    1. Create a new file called resources.properties in res.
    2. Set the fallback locale inside of this file with unqualifiedResLocale, eg:
      unqualifiedResLocale=en-US

Android 13 compatibility

Raise target level to Android 13

  • handle notification permission (docs)
  • predictive back gesture (docs)
    • Update androidx.appcompat:appcompat to 1.6.0-rc01 (Release notes)
  • per-app language preferences (docs)
  • themed icon (monochrome) (docs)
  • check whether anything else has changed
  • update libraries and dependencies
    • Update AGP to 7.3.1
      • Move package from Android manifest to build files (see)
      • Update desugaring lib to 1.2.0 (docs)
    • Update Kotlin to 1.7.20
    • Update androidx.test.runner to 1.5.0 (no breaking changes - Release notes)
    • Update androidx.test:rules to 1.5.0 (no breaking changes - Release notes)
    • Update com.google.android.material:material to 1.7.0 (Release notes)
      • Requirement of AGP >=7.2.0 (we are using 7.2.2 -> 7.3.1)
      • Requirement of Gradle >=7.3.3 (we are using 7.4.2)
      • Requirement of Java 8
    • Update androidx.core:core-ktx to 1.9.0 (Release notes - Improves compatibility with Android 13)
    • Update androidx.fragment:fragment-ktx to 1.5.4 (Release notes - Just bugfixes)
  • raise target level (targetSdkVersion) to 33
  • raise compile level (compileSdkVersion) to 33 and build tools (buildToolsVersion) to 33.0.0

(Dark mode?) after installing doesn't show calendar list

  1. (Switch device to dark mode)
  2. Install latest ICSx5 from Google Play, launch it, allow calendar access
  3. Add a subscription
  4. The subscription is there, but not shown – only a black screen, see screenshot below.

When the theme is changed to light theme or when ICSx⁵ is killed in the task manager and started again, everything works.

Screenshot_1662483490

Do Not Disturb cannot see subscribed .ics calendars

Discussed in #40

Originally posted by emacsomancer March 17, 2022
On an Android 10 device I was able to subscribe to an .ics calendar via ICSx5 and have Do Not Disturb turn on during calendar events from that calendar, but on my Android 12/GrapheneOS device Do Not Disturb doesn't seem to be able to 'see' that calendar (though it can see locally created calendars).

Provide backup over Backup API

Subscriptions and settings could be saved using the Android Backup API (also as a "test" for DAVx⁵ backup).

Automatic file backup should handle everything except the actual subscriptions because they reside in the calendar provider.

Maybe this can be implemented this way:

  1. Before the backup runs, ICSx⁵ saves the subscriptions to a private JSON file.
  2. Normal file backup runs.
  3. After a backup is restored, ICSx⁵ parses the JSON file and adds the subscriptions again.

Maybe credentials should only be saved when clientSideEncryption is available.

Depends on #76

Backup export and import

Following the discussion #69. Provide an option to export and import the database. This would provide backup functionality for subscriptions, but maybe we want to also move preferences to the database (eg. dark mode toggle) so it's exported together with the subscriptions.

Overall I'd say that the implementation steps would be:

  • Create a database import algorithm
  • Create a database export algorithm
  • Provide an extra option in the overflow menu in subscriptions list. Maybe we can add a "Backup" option, and then show a dialog to choose between import and export, or add a submenu to select this option. However, the second approach would not allow to show a warning to tell the users information about the backup, such as that importing would overwrite the existing data, or that backups are not encrypted.

ICSx⁵ crashes with a FileNotFound-Exception

Maybe this is happening when the user has the "edit view" open for a subscription that has been added before, but the subscription has been deleted already. This is happening on various Android versions and devices.

java.lang.RuntimeException: 
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:4022)
  at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:4188)
  at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:103)
  at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:135)
  at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:95)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2425)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:226)
  at android.os.Looper.loop (Looper.java:313)
  at android.app.ActivityThread.main (ActivityThread.java:8582)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:563)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1133)
Caused by: java.io.FileNotFoundException: 
  at at.bitfire.ical4android.AndroidCalendar$Companion.findByID (AndroidCalendar.kt:126)
  at at.bitfire.icsdroid.db.LocalCalendar$Companion.findById (LocalCalendar.kt:35)
  at at.bitfire.icsdroid.ui.EditCalendarActivity$CalendarModel.loadCalendar (EditCalendarActivity.kt:246)
  at at.bitfire.icsdroid.ui.EditCalendarActivity.onCreate (EditCalendarActivity.kt:82)
  at android.app.Activity.performCreate (Activity.java:8282)
  at android.app.Activity.performCreate (Activity.java:8262)
  at android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1329)
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3996)
  at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:4188)
  at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:103)
  at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:135)
  at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:95)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2425)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:226)
  at android.os.Looper.loop (Looper.java:313)
  at android.app.ActivityThread.main (ActivityThread.java:8582)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:563)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1133)

Typo on url launching

There's a typo in InfoActivity:

private fun launchUri(uri: Uri) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://twitter.com/icsx5app"))
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.w(Constants.TAG, "No browser installed")
}
}

It should add the uri from uri, not the hardcoded twitter url:

private fun launchUri(uri: Uri) {
    val intent = Intent(Intent.ACTION_VIEW, uri)
    try {
        startActivity(intent)
    } catch (e: ActivityNotFoundException) {
        Log.w(Constants.TAG, "No browser installed")
    }
}

ICSx⁵ crashes with a Security-Exception at at.bitfire.icsdroid.AppAccount.get

It seems the system can't access the calendar list of an ICSx⁵ account (at.bitfire.icsdroid.AppAccount.get)? Maybe we should just try-catch this and handle this case properly?

This happens on many different Samsung devices so far as well as on a Google Pixel 5 and so far only on Android 11 and 12.

java.lang.RuntimeException: 
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:4022)
  at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:4188)
  at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:103)
  at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:135)
  at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:95)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2425)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:226)
  at android.os.Looper.loop (Looper.java:313)
  at android.app.ActivityThread.main (ActivityThread.java:8582)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:563)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1133)
Caused by: java.lang.SecurityException: 
  at android.os.Parcel.createExceptionOrNull (Parcel.java:2437)
  at android.os.Parcel.createException (Parcel.java:2421)
  at android.os.Parcel.readException (Parcel.java:2404)
  at android.os.Parcel.readException (Parcel.java:2346)
  at android.accounts.IAccountManager$Stub$Proxy.addAccountExplicitly (IAccountManager.java:1550)
  at android.accounts.AccountManager.addAccountExplicitly (AccountManager.java:1030)
  at at.bitfire.icsdroid.AppAccount.get (AppAccount.kt:50)
  at at.bitfire.icsdroid.ui.CalendarListActivity$CalendarModel.loadCalendars (CalendarListActivity.kt:332)
  at at.bitfire.icsdroid.ui.CalendarListActivity$CalendarModel.startWatchingCalendars (CalendarListActivity.kt:318)
  at at.bitfire.icsdroid.ui.CalendarListActivity$CalendarModel.reinit (CalendarListActivity.kt:301)
  at at.bitfire.icsdroid.ui.CalendarListActivity.onCreate (CalendarListActivity.kt:98)
  at android.app.Activity.performCreate (Activity.java:8282)
  at android.app.Activity.performCreate (Activity.java:8262)
  at android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1329)
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3996)
  at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:4188)
  at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:103)
  at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:135)
  at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:95)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2425)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:226)
  at android.os.Looper.loop (Looper.java:313)
  at android.app.ActivityThread.main (ActivityThread.java:8582)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:563)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1133)
Caused by: android.os.RemoteException: 
  at com.android.server.accounts.AccountManagerService.addAccountExplicitlyWithVisibility (AccountManagerService.java:501)
  at com.android.server.accounts.AccountManagerService.addAccountExplicitly (AccountManagerService.java:1776)
  at android.accounts.IAccountManager$Stub.onTransact (IAccountManager.java:583)
  at com.android.server.accounts.AccountManagerService.onTransact (AccountManagerService.java:1119)
  at android.os.Binder.execTransactInternal (Binder.java:1215)

Workaround needed for AppleWebObjects

This has been reported by a user when adding certain calendars to ICSx5.

Adding: webcal://hhv-handball.liga.nu/cgi-bin/WebObjects/nuLigaHBDE.woa/wa/getTeamMeetingsWebcal?teamid=1457876 will throw an error:

HTTP 401 Apple WebObjects
java.io.IOException: HTTP 401 Apple WebObjects
at at.bitfire.icsdroid.CalendarFetcher.fetchNetwork$icsx5_62_2_0_2_gplayRelease(CalendarFetcher.kt:179)
at at.bitfire.icsdroid.CalendarFetcher.onRedirect(CalendarFetcher.kt:83)
at at.bitfire.icsdroid.CalendarFetcher.fetchNetwork$icsx5_62_2_0_2_gplayRelease(CalendarFetcher.kt:171)
at at.bitfire.icsdroid.CalendarFetcher.run(CalendarFetcher.kt:46)
at java.lang.Thread.run(Thread.java:1012)

While calendars from the same source with different events work:

webcal://hhv-handball.liga.nu/cgi-bin/WebObjects/nuLigaHBDE.woa/wa/getTeamMeetingsWebcal?teamid=1458151
webcal://hhv-handball.liga.nu/cgi-bin/WebObjects/nuLigaHBDE.woa/wa/getTeamMeetingsWebcal?teamid=1457879

Not super urgent but we should take look at this if we can workaround this.

Credentials should be stored in `EncryptedSharedPreferences`

Right now, credentials are being stored inside regular SharedPreferences in CalendarCredentials:

private val credentialPrefs = context.getSharedPreferences(PREF_CREDENTIALS, 0)

EncryptedSharedPreferences shall be used.

The change is quite simple, they provide a good example:

MasterKey masterKey = new MasterKey.Builder(context)
     .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
     .build();

 SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
     context,
     "secret_shared_prefs",
     masterKey,
     EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
     EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
 );

 // use the shared preferences and editor as you normally would
 SharedPreferences.Editor editor = sharedPreferences.edit();

[Mockup] UI overhaul for setup view

We should streamline the UI of to make it more clear for users how they can add subscriptions and with respect to the available options. With the newly added possibility to choose a file from any SAF capable document provider they can choose between two options how to add a subscription. By using tabs they can easily distinguish between these two options. "By URL" should be standard.

Used material icons: "link" and "folder_open".

grafik

Guidelines: https://material.io/components/tabs#anatomy

Subscriptions in database: UI

  • AddCalendarActivity creates Subscription in database instead of a local calendar
  • CalendarListActivity lists Subscriptions from DB instead of local calendars
  • EditCalendarActivity edits Subscription in database instead of local calendar
  • + all related fragments

Regarding permissions: we now don't have to ask for calendar permissions anymore in order to be able to create (manage) subscriptions because we have them in our DB. So I'd change the permission logic as follows:

  • As soon as there's at least one subscription, show some UI element in the CalendarListActivity that calendar permissions are required for synchronization. Users can then give the permission.
  • Permissions don't have to be checked elsewhere in the UI.
  • The synchronization worker has to check calendar permissions. If they're not granted, it should show a notification that leads to the CalendarListActivity.

Depends on #76

Update to AGP 8.0

ICSx5 should be updated to the latest libraries (including our own ones: cert4android, ical4android) and AGP 8.0, too.

Maybe we have to fiddle around with ProGuard; it would be important to generate a signed build (with some dummy key) and see whether it works.

See also what was necessary in DAVx5:

So I think these would be the tasks:

  • Update gradle to 8.0
  • Update AGP to 8.0
  • Use Java 17 in the workflows
  • Use latest own libraries
  • Update dependencies if necessary

Add copyright information in About dialog

I came across this because HUAWEI complained for "missing" copyright information. Missing in the sense that no entity is responsible for the uploaded app (and visible inside the app) - which is not compliant with their app distribution policy (anymore?). I also had to add our business license for DAVx5 last year and did so for ICSx5 now.

Currently we only have the GPLV3 link in the About dialog (for all stores the same) without any legal entity or names. I think we need to add bitfire as copyright owner for this like we do it with DAVx5. But I also think that we can keep the GPLV3 link. So then it is clearly shown that bitfire licenses the software under GPL and we also don't have to make flavors in Android studio to deal with different information for certain stores.

grafik

grafik

Error while adding meetup calendar to ICSx5 or DavX5

Steps to reproduce:

  1. Go to meetup.com > My Events
  2. Click on Sync Calendar > Outlook . Copy the link
  3. In ICS X5 , click on + button and paste link in ICSX5 in URL box
  4. Click on Next arrow on top right

Observed behavior

  1. Error message "Couldn't parse iCalendar
Couldn't parse iCalendar

at.bitfire.ical4android.InvalidCalendarException: Couldn't parse iCalendar
	at at.bitfire.ical4android.ICalendar$Companion.fromReader(ICalendar.kt:84)
	at at.bitfire.ical4android.Event$Companion.eventsFromReader(Event.kt:84)
	at at.bitfire.icsdroid.ui.AddCalendarValidationFragment$ValidationModel$initialize$downloader$1.onSuccess(AddCalendarValidationFragment.kt:114)
	at at.bitfire.icsdroid.CalendarFetcher.fetchNetwork(CalendarFetcher.kt:133)
	at at.bitfire.icsdroid.CalendarFetcher.onRedirect(CalendarFetcher.kt:79)
	at at.bitfire.icsdroid.CalendarFetcher.fetchNetwork(CalendarFetcher.kt:147)
	at at.bitfire.icsdroid.CalendarFetcher.run(CalendarFetcher.kt:44)
	at java.lang.Thread.run(Thread.java:764)
Caused by: net.fortuna.ical4j.data.ParserException: Error at line 149:Attempt to invoke virtual method 'void java.util.Calendar.setTimeZone(java.util.TimeZone)' on a null object reference
	at net.fortuna.ical4j.data.CalendarParserImpl.parse(CalendarParserImpl.java:162)
	at net.fortuna.ical4j.data.CalendarBuilder.build(CalendarBuilder.java:183)
	at net.fortuna.ical4j.data.CalendarBuilder.build(CalendarBuilder.java:171)
	at at.bitfire.ical4android.ICalendar$Companion.fromReader(ICalendar.kt:82)
	... 7 more
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void java.util.Calendar.setTimeZone(java.util.TimeZone)' on a null object reference
	at java.text.DateFormat.setTimeZone(DateFormat.java:690)
	at net.fortuna.ical4j.model.Iso8601.<init>(Iso8601.java:78)
	at net.fortuna.ical4j.model.Date.<init>(Date.java:150)
	at net.fortuna.ical4j.model.DateTime.<init>(DateTime.java:262)
	at net.fortuna.ical4j.model.TimeZone.inDaylightTime(TimeZone.java:143)
	at java.util.TimeZone.getOffsets(TimeZone.java:246)
	at java.util.GregorianCalendar.adjustForZoneAndDaylightSavingsTime(GregorianCalendar.java:2916)
	at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2807)
	at java.util.Calendar.updateTime(Calendar.java:3397)
	at java.util.Calendar.getTimeInMillis(Calendar.java:1761)
	at java.util.Calendar.getTime(Calendar.java:1734)
	at java.text.SimpleDateFormat.parseInternal(SimpleDateFormat.java:1633)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1528)
	at java.text.DateFormat.parse(DateFormat.java:360)
	at net.fortuna.ical4j.model.DateTime.setTime(DateTime.java:418)
	at net.fortuna.ical4j.model.DateTime.<init>(DateTime.java:325)
	at net.fortuna.ical4j.model.property.DateProperty.setValue(DateProperty.java:138)
	at net.fortuna.ical4j.data.DefaultContentHandler.resolveTimezones(DefaultContentHandler.java:218)
	at net.fortuna.ical4j.data.DefaultContentHandler.endCalendar(DefaultContentHandler.java:86)
	at net.fortuna.ical4j.data.CalendarParserImpl.parseCalendar(CalendarParserImpl.java:130)
	at net.fortuna.ical4j.data.CalendarParserImpl.parseCalendarList(CalendarParserImpl.java:184)
	at net.fortuna.ical4j.data.CalendarParserImpl.parse(CalendarParserImpl.java:153)
	... 10 more`

Add subscription crashes with invalid URL

Subscribing to an URL in the format https://abc@def:www.example.com/public/test.ics (invalid URL with swapped @ and :) crashes ICSx⁵ instead of giving an error message.

401 error for feeds with authorization and long password

Hello,

I wanted to subscribe to a calendar feed. After I entered my credentials and tried to connect, I got an error message with 401 error code.
When I looked on my password, I saw that it wasn't highlighted completely. So I changed my password on the remote site from 64 signs to a shorter one and now the subscription is working. Is there any password limit in ICSx?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.