diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index b2d9391a2b..74edf8eb34 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,25 +1,27 @@ name: Android CI with Gradle on: - pull_request: push: - workflow_dispatch: + branches: + - '*' + pull_request: + branches: [ master ] jobs: build: + runs-on: ubuntu-latest + steps: - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - name: set up JDK 17 - uses: actions/setup-java@v4.2.1 + uses: actions/checkout@v3 + - name: set up JDK 11 + uses: actions/setup-java@v3.6.0 with: distribution: 'zulu' - java-version: '17' + java-version: '11' - name: Restore Cache - uses: actions/cache@v4.0.2 + uses: actions/cache@v3.0.11 with: path: | ~/.gradle/caches @@ -27,13 +29,17 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} restore-keys: | ${{ runner.os }}-gradle- + - name: Checkout submodules # checkout rest + shell: bash + run: | + git submodule update --init - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/wrapper-validation-action@v1 - name: Build with Gradle run: | ./gradlew :app:assembleDebug - name: Upload APK - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: Etar_apk path: app/build/outputs/apk/debug/app-debug.apk diff --git a/README.md b/README.md index f1d0aae52a..b06cceaf43 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,11 @@ this app would be just a dream. So thanks to them! ## Features - Month view. - Week, day & agenda view. +- view task on day, week, month and agenda view - Uses Android calendar sync. Works with Google Calendar, Exchange, etc. - Material designed. - Support offline calendar. -- Agenda widget. +- Agenda and task widget. - Multilingual UI. ## How to use Etar @@ -36,6 +37,8 @@ Sync your calendar to a server: need yet another app, e. g. DAVx5. That’s necessary because a Caldav client isn't included in Etar. + The following [link](https://ownyourbits.com/2017/12/30/sync-nextcloud-tasks-calendars-and-contacts-on-your-android-device/) provides a tutorial how to use Nextcloud + DAVx5 + Etar. + ### Technical explanation On Android there are "Calendar providers". These can be calendars that are synchronized with a cloud service or local calendars. Basically any app diff --git a/app/build.gradle b/app/build.gradle index 8a5d0353cc..805094ccba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,15 +10,13 @@ editorconfig { } android { - namespace 'ws.xsoh.etar' - testNamespace 'com.android.calendar.tests' - compileSdk 34 + compileSdk 33 defaultConfig { minSdk 21 - targetSdk 34 - versionCode 43 - versionName "1.0.43" + targetSdk 33 + versionCode 34 + versionName "1.0.34" applicationId "ws.xsoh.etar" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -75,52 +73,42 @@ android { } lint { - lintConfig = file("lint.xml") - // TODO: Resolve lint errors due to 363aa9c237a33e9e1a40bdfd9039dcaaa855a5a0 + checkReleaseBuilds false + // Or, if you prefer, you can continue to check for errors in release builds, + // but continue the build even when errors are found: abortOnError false } compileOptions { coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "17" + jvmTarget = "11" } - - useLibrary 'android.test.base' - useLibrary 'android.test.mock' - - androidResources { - generateLocaleConfig true - } - } dependencies { // Core - implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.core:core-ktx:1.8.0' implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs') - implementation 'androidx.preference:preference:1.2.1' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'com.google.android.material:material:1.11.0' + implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'com.google.android.material:material:1.5.0' testImplementation 'junit:junit:4.13.2' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8' // Coroutines - def coroutines_version = "1.8.0" + def coroutines_version = "1.6.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" - // https://mvnrepository.com/artifact/org.dmfs/lib-recur - implementation 'org.dmfs:lib-recur:0.16.0' - } preBuild.dependsOn (":aarGen") diff --git a/app/lint.xml b/app/lint.xml deleted file mode 100644 index db997a5369..0000000000 --- a/app/lint.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3b1b75d19d..f1eaa681b5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,16 +30,19 @@ - - - + + + + - - - + + + + + @@ -52,6 +55,8 @@ + + @@ -268,9 +272,7 @@ - + diff --git a/app/src/main/assets/backward b/app/src/main/assets/backward index 421f2ec6b9..7685c7429c 100644 --- a/app/src/main/assets/backward +++ b/app/src/main/assets/backward @@ -4,33 +4,54 @@ # 2009-05-17 by Arthur David Olson. # This file provides links from old or merged timezone names to current ones. -# Many names changed in 1993 and in 1995, and many merged names moved here -# in the period from 2013 through 2022. Several of these names are +# Many names changed in late 1993. Several of these names are # also present in the file 'backzone', which has data important only # for pre-1970 timestamps and so is out of scope for tzdb proper. -# Although this file is optional and tzdb will work if you omit it by -# building with 'make BACKWARD=', in practice downstream users -# typically use this file for backward compatibility. - -# This file is divided into sections, one for each major reason for a -# backward compatibility link. Each section is sorted by link name. - -# A "#= TARGET1" comment labels each link inserted only because some -# .zi parsers (including tzcode through 2022e) mishandle links to links. -# The comment says what the target would be if these parsers were fixed -# so that data could contain links to links. For example, the line -# "Link Australia/Sydney Australia/ACT #= Australia/Canberra" would be -# "Link Australia/Canberra Australia/ACT" were it not that data lines -# refrain from linking to links like Australia/Canberra, which means -# the Australia/ACT line links instead to Australia/Sydney, -# Australia/Canberra's target. - - -# Pre-1993 naming conventions - -# Link TARGET LINK-NAME #= TARGET1 -Link Australia/Sydney Australia/ACT #= Australia/Canberra +# Link TARGET LINK-NAME +Link Africa/Nairobi Africa/Asmera +Link Africa/Abidjan Africa/Timbuktu +Link America/Argentina/Catamarca America/Argentina/ComodRivadavia +Link America/Adak America/Atka +Link America/Argentina/Buenos_Aires America/Buenos_Aires +Link America/Argentina/Catamarca America/Catamarca +Link America/Panama America/Coral_Harbour +Link America/Argentina/Cordoba America/Cordoba +Link America/Tijuana America/Ensenada +Link America/Indiana/Indianapolis America/Fort_Wayne +Link America/Nuuk America/Godthab +Link America/Indiana/Indianapolis America/Indianapolis +Link America/Argentina/Jujuy America/Jujuy +Link America/Indiana/Knox America/Knox_IN +Link America/Kentucky/Louisville America/Louisville +Link America/Argentina/Mendoza America/Mendoza +Link America/Toronto America/Montreal +Link America/Rio_Branco America/Porto_Acre +Link America/Argentina/Cordoba America/Rosario +Link America/Tijuana America/Santa_Isabel +Link America/Denver America/Shiprock +Link America/Puerto_Rico America/Virgin +Link Pacific/Auckland Antarctica/South_Pole +Link Asia/Ashgabat Asia/Ashkhabad +Link Asia/Kolkata Asia/Calcutta +Link Asia/Shanghai Asia/Chongqing +Link Asia/Shanghai Asia/Chungking +Link Asia/Dhaka Asia/Dacca +Link Asia/Shanghai Asia/Harbin +Link Asia/Urumqi Asia/Kashgar +Link Asia/Kathmandu Asia/Katmandu +Link Asia/Macau Asia/Macao +Link Asia/Yangon Asia/Rangoon +Link Asia/Ho_Chi_Minh Asia/Saigon +Link Asia/Jerusalem Asia/Tel_Aviv +Link Asia/Thimphu Asia/Thimbu +Link Asia/Makassar Asia/Ujung_Pandang +Link Asia/Ulaanbaatar Asia/Ulan_Bator +Link Atlantic/Faroe Atlantic/Faeroe +Link Europe/Oslo Atlantic/Jan_Mayen +Link Australia/Sydney Australia/ACT +Link Australia/Sydney Australia/Canberra +Link Australia/Hobart Australia/Currie Link Australia/Lord_Howe Australia/LHI Link Australia/Sydney Australia/NSW Link Australia/Darwin Australia/North @@ -40,7 +61,7 @@ Link Australia/Hobart Australia/Tasmania Link Australia/Melbourne Australia/Victoria Link Australia/Perth Australia/West Link Australia/Broken_Hill Australia/Yancowinna -Link America/Rio_Branco Brazil/Acre #= America/Porto_Acre +Link America/Rio_Branco Brazil/Acre Link America/Noronha Brazil/DeNoronha Link America/Sao_Paulo Brazil/East Link America/Manaus Brazil/West @@ -60,36 +81,17 @@ Link Pacific/Easter Chile/EasterIsland Link America/Havana Cuba Link Africa/Cairo Egypt Link Europe/Dublin Eire -# Vanguard section, for most .zi parsers. -#Link GMT Etc/GMT -#Link GMT Etc/GMT+0 -#Link GMT Etc/GMT-0 -#Link GMT Etc/GMT0 -#Link GMT Etc/Greenwich -# Rearguard section, for TZUpdater 2.3.2 and earlier. -Link Etc/GMT Etc/GMT+0 -Link Etc/GMT Etc/GMT-0 -Link Etc/GMT Etc/GMT0 -Link Etc/GMT Etc/Greenwich -# End of rearguard section. Link Etc/UTC Etc/UCT -Link Etc/UTC Etc/Universal -Link Etc/UTC Etc/Zulu +Link Europe/London Europe/Belfast +Link Europe/Chisinau Europe/Tiraspol Link Europe/London GB Link Europe/London GB-Eire -# Vanguard section, for most .zi parsers. -#Link GMT GMT+0 -#Link GMT GMT-0 -#Link GMT GMT0 -#Link GMT Greenwich -# Rearguard section, for TZUpdater 2.3.2 and earlier. Link Etc/GMT GMT+0 Link Etc/GMT GMT-0 Link Etc/GMT GMT0 Link Etc/GMT Greenwich -# End of rearguard section. Link Asia/Hong_Kong Hongkong -Link Africa/Abidjan Iceland #= Atlantic/Reykjavik +Link Atlantic/Reykjavik Iceland Link Asia/Tehran Iran Link Asia/Jerusalem Israel Link America/Jamaica Jamaica @@ -101,8 +103,14 @@ Link America/Mazatlan Mexico/BajaSur Link America/Mexico_City Mexico/General Link Pacific/Auckland NZ Link Pacific/Chatham NZ-CHAT -Link America/Denver Navajo #= America/Shiprock +Link America/Denver Navajo Link Asia/Shanghai PRC +Link Pacific/Kanton Pacific/Enderbury +Link Pacific/Honolulu Pacific/Johnston +Link Pacific/Pohnpei Pacific/Ponape +Link Pacific/Pago_Pago Pacific/Samoa +Link Pacific/Chuuk Pacific/Truk +Link Pacific/Chuuk Pacific/Yap Link Europe/Warsaw Poland Link Europe/Lisbon Portugal Link Asia/Taipei ROC @@ -126,194 +134,3 @@ Link Etc/UTC UTC Link Etc/UTC Universal Link Europe/Moscow W-SU Link Etc/UTC Zulu - - -# Two-part names that were renamed mostly to three-part names in 1995 - -# Link TARGET LINK-NAME #= TARGET1 -Link America/Argentina/Buenos_Aires America/Buenos_Aires -Link America/Argentina/Catamarca America/Catamarca -Link America/Argentina/Cordoba America/Cordoba -Link America/Indiana/Indianapolis America/Indianapolis -Link America/Argentina/Jujuy America/Jujuy -Link America/Indiana/Knox America/Knox_IN -Link America/Kentucky/Louisville America/Louisville -Link America/Argentina/Mendoza America/Mendoza -Link America/Puerto_Rico America/Virgin #= America/St_Thomas -Link Pacific/Pago_Pago Pacific/Samoa - - -# Pre-2013 practice, which typically had a Zone per zone.tab line - -# Link TARGET LINK-NAME -Link Africa/Abidjan Africa/Accra -Link Africa/Nairobi Africa/Addis_Ababa -Link Africa/Nairobi Africa/Asmara -Link Africa/Abidjan Africa/Bamako -Link Africa/Lagos Africa/Bangui -Link Africa/Abidjan Africa/Banjul -Link Africa/Maputo Africa/Blantyre -Link Africa/Lagos Africa/Brazzaville -Link Africa/Maputo Africa/Bujumbura -Link Africa/Abidjan Africa/Conakry -Link Africa/Abidjan Africa/Dakar -Link Africa/Nairobi Africa/Dar_es_Salaam -Link Africa/Nairobi Africa/Djibouti -Link Africa/Lagos Africa/Douala -Link Africa/Abidjan Africa/Freetown -Link Africa/Maputo Africa/Gaborone -Link Africa/Maputo Africa/Harare -Link Africa/Nairobi Africa/Kampala -Link Africa/Maputo Africa/Kigali -Link Africa/Lagos Africa/Kinshasa -Link Africa/Lagos Africa/Libreville -Link Africa/Abidjan Africa/Lome -Link Africa/Lagos Africa/Luanda -Link Africa/Maputo Africa/Lubumbashi -Link Africa/Maputo Africa/Lusaka -Link Africa/Lagos Africa/Malabo -Link Africa/Johannesburg Africa/Maseru -Link Africa/Johannesburg Africa/Mbabane -Link Africa/Nairobi Africa/Mogadishu -Link Africa/Lagos Africa/Niamey -Link Africa/Abidjan Africa/Nouakchott -Link Africa/Abidjan Africa/Ouagadougou -Link Africa/Lagos Africa/Porto-Novo -Link America/Puerto_Rico America/Anguilla -Link America/Puerto_Rico America/Antigua -Link America/Puerto_Rico America/Aruba -Link America/Panama America/Atikokan -Link America/Puerto_Rico America/Blanc-Sablon -Link America/Panama America/Cayman -Link America/Phoenix America/Creston -Link America/Puerto_Rico America/Curacao -Link America/Puerto_Rico America/Dominica -Link America/Puerto_Rico America/Grenada -Link America/Puerto_Rico America/Guadeloupe -Link America/Puerto_Rico America/Kralendijk -Link America/Puerto_Rico America/Lower_Princes -Link America/Puerto_Rico America/Marigot -Link America/Puerto_Rico America/Montserrat -Link America/Toronto America/Nassau -Link America/Puerto_Rico America/Port_of_Spain -Link America/Puerto_Rico America/St_Barthelemy -Link America/Puerto_Rico America/St_Kitts -Link America/Puerto_Rico America/St_Lucia -Link America/Puerto_Rico America/St_Thomas -Link America/Puerto_Rico America/St_Vincent -Link America/Puerto_Rico America/Tortola -Link Pacific/Port_Moresby Antarctica/DumontDUrville -Link Pacific/Auckland Antarctica/McMurdo -Link Asia/Riyadh Antarctica/Syowa -Link Asia/Urumqi Antarctica/Vostok -Link Europe/Berlin Arctic/Longyearbyen -Link Asia/Riyadh Asia/Aden -Link Asia/Qatar Asia/Bahrain -Link Asia/Kuching Asia/Brunei -Link Asia/Singapore Asia/Kuala_Lumpur -Link Asia/Riyadh Asia/Kuwait -Link Asia/Dubai Asia/Muscat -Link Asia/Bangkok Asia/Phnom_Penh -Link Asia/Bangkok Asia/Vientiane -Link Africa/Abidjan Atlantic/Reykjavik -Link Africa/Abidjan Atlantic/St_Helena -Link Europe/Brussels Europe/Amsterdam -Link Europe/Prague Europe/Bratislava -Link Europe/Zurich Europe/Busingen -Link Europe/Berlin Europe/Copenhagen -Link Europe/London Europe/Guernsey -Link Europe/London Europe/Isle_of_Man -Link Europe/London Europe/Jersey -Link Europe/Belgrade Europe/Ljubljana -Link Europe/Brussels Europe/Luxembourg -Link Europe/Helsinki Europe/Mariehamn -Link Europe/Paris Europe/Monaco -Link Europe/Berlin Europe/Oslo -Link Europe/Belgrade Europe/Podgorica -Link Europe/Rome Europe/San_Marino -Link Europe/Belgrade Europe/Sarajevo -Link Europe/Belgrade Europe/Skopje -Link Europe/Berlin Europe/Stockholm -Link Europe/Zurich Europe/Vaduz -Link Europe/Rome Europe/Vatican -Link Europe/Belgrade Europe/Zagreb -Link Africa/Nairobi Indian/Antananarivo -Link Asia/Bangkok Indian/Christmas -Link Asia/Yangon Indian/Cocos -Link Africa/Nairobi Indian/Comoro -Link Indian/Maldives Indian/Kerguelen -Link Asia/Dubai Indian/Mahe -Link Africa/Nairobi Indian/Mayotte -Link Asia/Dubai Indian/Reunion -Link Pacific/Port_Moresby Pacific/Chuuk -Link Pacific/Tarawa Pacific/Funafuti -Link Pacific/Tarawa Pacific/Majuro -Link Pacific/Pago_Pago Pacific/Midway -Link Pacific/Guadalcanal Pacific/Pohnpei -Link Pacific/Guam Pacific/Saipan -Link Pacific/Tarawa Pacific/Wake -Link Pacific/Tarawa Pacific/Wallis - - -# Non-zone.tab locations with timestamps since 1970 that duplicate -# those of an existing location - -# Link TARGET LINK-NAME -Link Africa/Abidjan Africa/Timbuktu -Link America/Argentina/Catamarca America/Argentina/ComodRivadavia -Link America/Adak America/Atka -Link America/Panama America/Coral_Harbour -Link America/Tijuana America/Ensenada -Link America/Indiana/Indianapolis America/Fort_Wayne -Link America/Toronto America/Montreal -Link America/Toronto America/Nipigon -Link America/Iqaluit America/Pangnirtung -Link America/Rio_Branco America/Porto_Acre -Link America/Winnipeg America/Rainy_River -Link America/Argentina/Cordoba America/Rosario -Link America/Tijuana America/Santa_Isabel -Link America/Denver America/Shiprock -Link America/Toronto America/Thunder_Bay -Link America/Edmonton America/Yellowknife -Link Pacific/Auckland Antarctica/South_Pole -Link Asia/Shanghai Asia/Chongqing -Link Asia/Shanghai Asia/Harbin -Link Asia/Urumqi Asia/Kashgar -Link Asia/Jerusalem Asia/Tel_Aviv -Link Europe/Berlin Atlantic/Jan_Mayen -Link Australia/Sydney Australia/Canberra -Link Australia/Hobart Australia/Currie -Link Europe/London Europe/Belfast -Link Europe/Chisinau Europe/Tiraspol -Link Europe/Kyiv Europe/Uzhgorod -Link Europe/Kyiv Europe/Zaporozhye -Link Pacific/Kanton Pacific/Enderbury -Link Pacific/Honolulu Pacific/Johnston -Link Pacific/Port_Moresby Pacific/Yap - - -# Alternate names for the same location - -# Link TARGET LINK-NAME #= TARGET1 -Link Africa/Nairobi Africa/Asmera #= Africa/Asmara -Link America/Nuuk America/Godthab -Link Asia/Ashgabat Asia/Ashkhabad -Link Asia/Kolkata Asia/Calcutta -Link Asia/Shanghai Asia/Chungking #= Asia/Chongqing -Link Asia/Dhaka Asia/Dacca -# Istanbul is in both continents. -Link Europe/Istanbul Asia/Istanbul -Link Asia/Kathmandu Asia/Katmandu -Link Asia/Macau Asia/Macao -Link Asia/Yangon Asia/Rangoon -Link Asia/Ho_Chi_Minh Asia/Saigon -Link Asia/Thimphu Asia/Thimbu -Link Asia/Makassar Asia/Ujung_Pandang -Link Asia/Ulaanbaatar Asia/Ulan_Bator -Link Atlantic/Faroe Atlantic/Faeroe -Link Europe/Kyiv Europe/Kiev -# Classically, Cyprus is in Asia; e.g. see Herodotus, Histories, I.72. -# However, for various reasons many users expect to find it under Europe. -Link Asia/Nicosia Europe/Nicosia -Link Pacific/Guadalcanal Pacific/Ponape #= Pacific/Pohnpei -Link Pacific/Port_Moresby Pacific/Truk #= Pacific/Chuuk diff --git a/app/src/main/assets/zone.tab b/app/src/main/assets/zone.tab index dbcb61793e..086458fb20 100644 --- a/app/src/main/assets/zone.tab +++ b/app/src/main/assets/zone.tab @@ -114,18 +114,23 @@ CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) -CA +6344-06828 America/Iqaluit Eastern - NU (most areas) +CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) +CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) +CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) +CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba +CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); NT (E); SK (W) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) +CA +6227-11421 America/Yellowknife Mountain - NT (central) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +4906-11631 America/Creston MST - BC (Creston) -CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) +CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) CA +6043-13503 America/Whitehorse MST - Yukon (east) CA +6404-13925 America/Dawson MST - Yukon (west) @@ -138,7 +143,7 @@ CG -0416+01517 Africa/Brazzaville CH +4723+00832 Europe/Zurich CI +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago most of Chile +CL -3327-07040 America/Santiago Chile (most areas) CL -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CM +0403+00942 Africa/Douala @@ -150,10 +155,10 @@ CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde CW +1211-06900 America/Curacao CX -1025+10543 Indian/Christmas -CY +3510+03322 Asia/Nicosia most of Cyprus +CY +3510+03322 Asia/Nicosia Cyprus (most areas) CY +3507+03357 Asia/Famagusta Northern Cyprus CZ +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin most of Germany +DE +5230+01322 Europe/Berlin Germany (most areas) DE +4742+00841 Europe/Busingen Busingen DJ +1136+04309 Africa/Djibouti DK +5540+01235 Europe/Copenhagen @@ -186,7 +191,7 @@ GF +0456-05220 America/Cayenne GG +492717-0023210 Europe/Guernsey GH +0533-00013 Africa/Accra GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Nuuk most of Greenland +GL +6411-05144 America/Nuuk Greenland (most areas) GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik @@ -234,7 +239,7 @@ KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul KW +2920+04759 Asia/Kuwait KY +1918-08123 America/Cayman -KZ +4315+07657 Asia/Almaty most of Kazakhstan +KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe @@ -258,12 +263,12 @@ MD +4700+02850 Europe/Chisinau ME +4226+01916 Europe/Podgorica MF +1804-06305 America/Marigot MG -1855+04731 Indian/Antananarivo -MH +0709+17112 Pacific/Majuro most of Marshall Islands +MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) MH +0905+16720 Pacific/Kwajalein Kwajalein MK +4159+02126 Europe/Skopje ML +1239-00800 Africa/Bamako MM +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar most of Mongolia +MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar MO +221150+1133230 Asia/Macau @@ -275,18 +280,17 @@ MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius MV +0410+07330 Indian/Maldives MW -1547+03500 Africa/Blantyre -MX +1924-09909 America/Mexico_City Central Mexico -MX +2105-08646 America/Cancun Quintana Roo -MX +2058-08937 America/Merida Campeche, Yucatan -MX +2540-10019 America/Monterrey Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas) -MX +2550-09730 America/Matamoros Coahuila, Nuevo Leon, Tamaulipas (US border) -MX +2838-10605 America/Chihuahua Chihuahua (most areas) -MX +3144-10629 America/Ciudad_Juarez Chihuahua (US border - west) -MX +2934-10425 America/Ojinaga Chihuahua (US border - east) -MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinaloa -MX +2048-10515 America/Bahia_Banderas Bahia de Banderas -MX +2904-11058 America/Hermosillo Sonora -MX +3232-11701 America/Tijuana Baja California +MX +1924-09909 America/Mexico_City Central Time +MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo +MX +2058-08937 America/Merida Central Time - Campeche, Yucatan +MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas) +MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo Leon, Tamaulipas (US border) +MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa +MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) +MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) +MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora +MX +3232-11701 America/Tijuana Pacific Time US - Baja California +MX +2048-10515 America/Bahia_Banderas Central Time - Bahia de Banderas MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) MY +0133+11020 Asia/Kuching Sabah, Sarawak MZ -2558+03235 Africa/Maputo @@ -301,7 +305,7 @@ NO +5955+01045 Europe/Oslo NP +2743+08519 Asia/Kathmandu NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue -NZ -3652+17446 Pacific/Auckland most of New Zealand +NZ -3652+17446 Pacific/Auckland New Zealand (most areas) NZ -4357-17633 Pacific/Chatham Chatham Islands OM +2336+05835 Asia/Muscat PA +0858-07932 America/Panama @@ -309,7 +313,7 @@ PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands -PG -0930+14710 Pacific/Port_Moresby most of Papua New Guinea +PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) PG -0613+15534 Pacific/Bougainville Bougainville PH +1435+12100 Asia/Manila PK +2452+06703 Asia/Karachi @@ -355,7 +359,7 @@ RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky RU +5934+15048 Asia/Magadan MSK+08 - Magadan RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); N Kuril Is +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea RW -0157+03004 Africa/Kigali @@ -396,7 +400,9 @@ TT +1039-06131 America/Port_of_Spain TV -0831+17913 Pacific/Funafuti TW +2503+12130 Asia/Taipei TZ -0648+03917 Africa/Dar_es_Salaam -UA +5026+03031 Europe/Kyiv most of Ukraine +UA +5026+03031 Europe/Kiev Ukraine (most areas) +UA +4837+02218 Europe/Uzhgorod Transcarpathia +UA +4750+03510 Europe/Zaporozhye Zaporozhye and east Lugansk UG +0019+03225 Africa/Kampala UM +2813-17722 Pacific/Midway Midway Islands UM +1917+16637 Pacific/Wake Wake Island @@ -419,7 +425,7 @@ US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US +332654-1120424 America/Phoenix MST - AZ (except Navajo) +US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area @@ -427,7 +433,7 @@ US +571035-1351807 America/Sitka Alaska - Sitka area US +550737-1313435 America/Metlakatla Alaska - Annette Island US +593249-1394338 America/Yakutat Alaska - Yakutat US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Alaska - western Aleutians +US +515248-1763929 America/Adak Aleutian Islands US +211825-1575130 Pacific/Honolulu Hawaii UY -345433-0561245 America/Montevideo UZ +3940+06648 Asia/Samarkand Uzbekistan (west) diff --git a/app/src/main/java/com/android/calendar/AllInOneActivity.java b/app/src/main/java/com/android/calendar/AllInOneActivity.java index 80a2e87bdf..bd527f063a 100644 --- a/app/src/main/java/com/android/calendar/AllInOneActivity.java +++ b/app/src/main/java/com/android/calendar/AllInOneActivity.java @@ -27,6 +27,9 @@ import android.animation.Animator.AnimatorListener; import android.animation.ObjectAnimator; import android.app.DatePickerDialog; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; import android.content.AsyncQueryHandler; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -72,9 +75,6 @@ import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; import com.android.calendar.CalendarController.EventHandler; import com.android.calendar.CalendarController.EventInfo; @@ -83,6 +83,7 @@ import com.android.calendar.agenda.AgendaFragment; import com.android.calendar.alerts.AlertService; import com.android.calendar.month.MonthByWeekFragment; +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract; import com.android.calendar.selectcalendars.SelectVisibleCalendarsFragment; import com.android.calendar.settings.GeneralPreferences; import com.android.calendar.settings.SettingsActivity; @@ -208,6 +209,8 @@ public void onAnimationStart(android.animation.Animator animation) { private MenuItem mSearchMenu; private MenuItem mControlsMenu; private MenuItem mViewSettings; + private MenuItem mViewAgendaEvents; + private MenuItem mViewAgendaTasks; private Menu mOptionsMenu; private QueryHandler mHandler; private final Runnable mHomeTimeUpdater = new Runnable() { @@ -241,7 +244,7 @@ public void run() { protected void onNewIntent(Intent intent) { String action = intent.getAction(); if (DEBUG) - Log.d(TAG, "New intent received " + intent); + Log.d(TAG, "New intent received " + intent.toString()); // Don't change the date if we're just returning to the app's home if (Intent.ACTION_VIEW.equals(action) && !intent.getBooleanExtra(Utils.INTENT_KEY_HOME, false)) { @@ -267,12 +270,12 @@ protected void onCreate(Bundle icicle) { // This needs to be created before setContentView mController = CalendarController.getInstance(this); + // Create notification channel + AlertService.createChannels(this); + // Check and ask for most needed permissions checkAppPermissions(); - // Create notification channels - AlertService.createChannels(this); - // Get time from intent or icicle long timeMillis = -1; int viewType = -1; @@ -301,7 +304,7 @@ protected void onCreate(Bundle icicle) { if (DEBUG) { if (icicle != null && intent != null) { - Log.d(TAG, "both, icicle:" + icicle + " intent:" + intent); + Log.d(TAG, "both, icicle:" + icicle.toString() + " intent:" + intent.toString()); } else { Log.d(TAG, "not both, icicle:" + icicle + " intent:" + intent); } @@ -398,13 +401,19 @@ private void checkAppPermissions() { != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED)) { + != PackageManager.PERMISSION_GRANTED) || + ContextCompat.checkSelfPermission(this, DmfsOpenTasksContract.TASK_READ_PERMISSION) + != PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(this, DmfsOpenTasksContract.TASK_WRITE_PERMISSION) + != PackageManager.PERMISSION_GRANTED) { ArrayList permissionsList = new ArrayList<>(Arrays.asList( Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR, - Manifest.permission.WRITE_EXTERNAL_STORAGE) - ); + Manifest.permission.WRITE_EXTERNAL_STORAGE, + DmfsOpenTasksContract.TASK_READ_PERMISSION, + DmfsOpenTasksContract.TASK_WRITE_PERMISSION + )); // Permission for calendar notifications if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && @@ -420,13 +429,10 @@ private void checkAppPermissions() { permissionsList.toArray(permissionsArray), PERMISSIONS_REQUEST_WRITE_CALENDAR); } - } private void checkAndRequestDisablingDoze() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; - boolean doNotCheckBatteryOptimization = Utils.getSharedPreference(getApplicationContext(), GeneralPreferences.KEY_DO_NOT_CHECK_BATTERY_OPTIMIZATION, false); - if (!dozeDisabled() && !doNotCheckBatteryOptimization) { + if (!dozeDisabled()) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + getApplicationContext().getPackageName())); @@ -435,8 +441,6 @@ private void checkAndRequestDisablingDoze() { } private Boolean dozeDisabled() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true; - String packageName = getApplicationContext().getPackageName(); PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE); return pm.isIgnoringBatteryOptimizations(packageName); @@ -600,14 +604,6 @@ protected void onResume() { mController.registerFirstEventHandler(HANDLER_KEY, this); mOnSaveInstanceStateCalled = false; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - if (!Utils.canScheduleAlarms(this)) { - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); - startActivity(intent); - } - } - if (!Utils.isCalendarPermissionGranted(this, true)) { //If permission is not granted then just return. Log.d(TAG, "Manifest.permission.READ_CALENDAR is not granted"); @@ -696,7 +692,7 @@ public void onSaveInstanceState(Bundle outState) { if (mCurrentView == ViewType.EDIT) { outState.putLong(BUNDLE_KEY_EVENT_ID, mController.getEventId()); } else if (mCurrentView == ViewType.AGENDA) { - FragmentManager fm = getSupportFragmentManager(); + FragmentManager fm = getFragmentManager(); Fragment f = fm.findFragmentById(R.id.main_pane); if (f instanceof AgendaFragment) { outState.putLong(BUNDLE_KEY_EVENT_ID, ((AgendaFragment) f).getLastShowEventId()); @@ -749,7 +745,7 @@ private void initFragments(long timeMillis, int viewType, Bundle icicle) { if (DEBUG) { Log.d(TAG, "Initializing to " + timeMillis + " for view " + viewType); } - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + FragmentTransaction ft = getFragmentManager().beginTransaction(); if (mShowCalendarControls) { Fragment miniMonthFrag = new MonthByWeekFragment(timeMillis, true); @@ -837,6 +833,31 @@ protected void updateViewSettingsVisiblility() { } } + protected void updateViewAgentaSwitchVisibility() { + if (ContextCompat.checkSelfPermission(this, DmfsOpenTasksContract.TASK_READ_PERMISSION) + != PackageManager.PERMISSION_GRANTED) { + if (mViewAgendaTasks != null) { + mViewAgendaTasks.setVisible(false); + mViewAgendaTasks.setEnabled(false); + } + if (mViewAgendaEvents != null) { + mViewAgendaEvents.setVisible(false); + mViewAgendaEvents.setEnabled(false); + } + return; + } + + boolean viewAgendaSwitchVisible = mController.getViewType() == ViewType.AGENDA; + if (mViewAgendaTasks != null) { + mViewAgendaTasks.setVisible(viewAgendaSwitchVisible); + mViewAgendaTasks.setEnabled(viewAgendaSwitchVisible); + } + if (mViewAgendaEvents != null) { + mViewAgendaEvents.setVisible(viewAgendaSwitchVisible && !mViewAgendaTasks.isVisible()); + mViewAgendaEvents.setEnabled(viewAgendaSwitchVisible); + } + } + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); @@ -881,6 +902,9 @@ public boolean onCreateOptionsMenu(Menu menu) { mViewSettings = menu.findItem(R.id.action_view_settings); updateViewSettingsVisiblility(); + mViewAgendaEvents = menu.findItem(R.id.action_view_agenda_events); + mViewAgendaTasks = menu.findItem(R.id.action_view_agenda_tasks); + updateViewAgentaSwitchVisibility(); MenuItem menuItem = menu.findItem(R.id.action_today); @@ -890,8 +914,7 @@ public boolean onCreateOptionsMenu(Menu menu) { Utils.setTodayIcon(icon, this, mTimeZone); // Handle warning for disabling battery optimizations - boolean doNotCheckBatteryOptimization = Utils.getSharedPreference(getApplicationContext(), GeneralPreferences.KEY_DO_NOT_CHECK_BATTERY_OPTIMIZATION, false); - if (dozeDisabled() || doNotCheckBatteryOptimization) { + if (dozeDisabled()) { MenuItem menuInfoItem = menu.findItem(R.id.action_info); if (menuInfoItem != null) { menuInfoItem.setVisible(false); @@ -980,8 +1003,28 @@ public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth startActivity(intent); } else if (itemId == R.id.action_info) { checkAndRequestDisablingDoze(); + } else if (itemId == R.id.action_view_agenda_tasks || itemId == R.id.action_view_agenda_events) { + FragmentManager manager = getFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction(); + long millis = Utils.timeFromIntentInMillis(getIntent()); + AgendaFragment frag = new AgendaFragment(millis, false); + + if (itemId == R.id.action_view_agenda_tasks) { + frag.isTask = true; + mOptionsMenu.findItem(R.id.action_view_agenda_events).setVisible(true); + } else if (itemId == R.id.action_view_agenda_events) { + frag.isTask = false; + mOptionsMenu.findItem(R.id.action_view_agenda_tasks).setVisible(true); + } + item.setVisible(false); + + transaction.replace(R.id.main_pane, frag); + mController.registerEventHandler(R.id.main_pane, (EventHandler) frag); + transaction.commit(); + + return false; } else { - return mExtensions.handleItemSelected(item, this); + return mExtensions.handleItemSelected(item, this); } return true; @@ -1069,7 +1112,7 @@ private void setMainPane( // Remove this when transition to and from month view looks fine. boolean doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH; - FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentManager fragmentManager = getFragmentManager(); // Check if our previous view was an Agenda view // TODO remove this if framework ever supports nested fragments if (mCurrentView == ViewType.AGENDA) { @@ -1286,7 +1329,7 @@ private void updateSecondaryTitleFields(long visibleMillisSinceEpoch) { @Override public long getSupportedEventTypes() { - return EventType.GO_TO | EventType.VIEW_EVENT | EventType.UPDATE_TITLE; + return EventType.GO_TO | EventType.VIEW_EVENT | EventType.UPDATE_TITLE | EventType.VIEW_TASK; } @Override @@ -1353,6 +1396,7 @@ public void handleEvent(EventInfo event) { } } updateViewSettingsVisiblility(); + updateViewAgentaSwitchVisibility(); displayTime = event.selectedTime != null ? event.selectedTime.toMillis() : event.startTime.toMillis(); if (!mIsTabletConfig) { @@ -1410,7 +1454,74 @@ public void handleEvent(EventInfo event) { EventInfoFragment.DIALOG_WINDOW_STYLE, null /* No reminders to explicitly pass in. */); fragment.setDialogParams(event.x, event.y, mActionBar.getHeight()); - FragmentManager fm = getSupportFragmentManager(); + FragmentManager fm = getFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + // if we have an old popup replace it + Fragment fOld = fm.findFragmentByTag(EVENT_INFO_FRAGMENT_TAG); + if (fOld != null && fOld.isAdded()) { + ft.remove(fOld); + } + ft.add(fragment, EVENT_INFO_FRAGMENT_TAG); + ft.commit(); + } + } + displayTime = event.startTime.toMillis(); + } else if (event.eventType == EventType.VIEW_TASK) { + + // If in Agenda view and "show_event_details_with_agenda" is "true", + // do not create the event info fragment here, it will be created by the Agenda + // fragment + + if (mCurrentView == ViewType.AGENDA && mShowEventDetailsWithAgenda) { + if (event.startTime != null && event.endTime != null) { + // Event is all day , adjust the goto time to local time + if (event.isAllDay()) { + Utils.convertAlldayUtcToLocal(event.startTime, event.startTime.toMillis(), mTimeZone); + Utils.convertAlldayUtcToLocal(event.endTime, event.endTime.toMillis(), mTimeZone); + } + mController.sendEvent(this, EventType.GO_TO, + event.startTime, event.endTime, event.selectedTime, + event.id, ViewType.AGENDA, + CalendarController.EXTRA_GOTO_TIME, null, null); + } else if (event.selectedTime != null) { + mController.sendEvent(this, EventType.GO_TO, + event.selectedTime, event.selectedTime, event.id, + ViewType.AGENDA); + } + } else { + // TODO Fix the temp hack below: && mCurrentView != + // ViewType.AGENDA + if (event.selectedTime != null && mCurrentView != ViewType.AGENDA) { + mController.sendEvent(this, EventType.GO_TO, + event.selectedTime, event.selectedTime, -1, + ViewType.CURRENT); + } + int response = event.getResponse(); + if ((mCurrentView == ViewType.AGENDA && + mShowEventInfoFullScreenAgenda) || ((mCurrentView + == ViewType.DAY || (mCurrentView == + ViewType.WEEK) || mCurrentView == + ViewType.MONTH) && + mShowEventInfoFullScreen)) { + // start event info as activity + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri eventUri = ContentUris.withAppendedId(DmfsOpenTasksContract.Tasks.PROVIDER_URI, event.id); + intent.setData(eventUri); + intent.setClass(this, EventInfoActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(EXTRA_EVENT_BEGIN_TIME, event.startTime.toMillis()); + intent.putExtra(EXTRA_EVENT_END_TIME, event.endTime.toMillis()); + intent.putExtra(ATTENDEE_STATUS, response); + startActivity(intent); + } else { + // start event info as a dialog + EventInfoFragment fragment = new EventInfoFragment(this, + event.id, event.startTime.toMillis(), + event.endTime.toMillis(), response, true, + EventInfoFragment.DIALOG_WINDOW_STYLE, + null /* No reminders to explicitly pass in. */); + fragment.setDialogParams(event.x, event.y, mActionBar.getHeight()); + FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); // if we have an old popup replace it Fragment fOld = fm.findFragmentByTag(EVENT_INFO_FRAGMENT_TAG); diff --git a/app/src/main/java/com/android/calendar/CalendarController.java b/app/src/main/java/com/android/calendar/CalendarController.java index ec71caa55b..e01544721b 100644 --- a/app/src/main/java/com/android/calendar/CalendarController.java +++ b/app/src/main/java/com/android/calendar/CalendarController.java @@ -40,6 +40,7 @@ import android.util.Pair; import com.android.calendar.event.EditEventActivity; +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract; import com.android.calendar.settings.GeneralPreferences; import com.android.calendar.settings.SettingsActivity; import com.android.calendarcommon2.Time; @@ -426,6 +427,10 @@ public void sendEvent(Object sender, final EventInfo event) { launchViewEvent(event.id, event.startTime.toMillis(), endTime, event.getResponse()); return; + } else if (event.eventType == EventType.VIEW_TASK) { + launchViewTask(event.id, event.startTime.toMillis(), endTime, + event.getResponse()); + return; } else if (event.eventType == EventType.EDIT_EVENT) { launchEditEvent(event.id, event.startTime.toMillis(), endTime, true); return; @@ -589,6 +594,18 @@ public void launchViewEvent(long eventId, long startMillis, long endMillis, int mContext.startActivity(intent); } + public void launchViewTask(long eventId, long startMillis, long endMillis, int response) { + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri eventUri = ContentUris.withAppendedId(DmfsOpenTasksContract.Tasks.PROVIDER_URI, eventId); + intent.setData(eventUri); + intent.setClass(mContext, AllInOneActivity.class); + intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis); + intent.putExtra(EXTRA_EVENT_END_TIME, endMillis); + intent.putExtra(ATTENDEE_STATUS, response); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + private void launchEditEvent(long eventId, long startMillis, long endMillis, boolean edit) { Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); Intent intent = new Intent(Intent.ACTION_EDIT, uri); @@ -661,6 +678,8 @@ private String eventInfoToString(EventInfo eventInfo) { tmp = "View details"; } else if ((eventInfo.eventType & EventType.EDIT_EVENT) != 0) { tmp = "Edit event"; + } else if ((eventInfo.eventType & EventType.VIEW_TASK) != 0) { + tmp = "View task"; } else if ((eventInfo.eventType & EventType.DELETE_EVENT) != 0) { tmp = "Delete event"; } else if ((eventInfo.eventType & EventType.LAUNCH_SETTINGS) != 0) { @@ -722,6 +741,8 @@ public interface EventType { // date range has changed, update the title final long UPDATE_TITLE = 1L << 10; + + final long VIEW_TASK = 1L << 11; } /** @@ -833,7 +854,7 @@ public boolean isAllDay() { } public int getResponse() { - if (eventType != EventType.VIEW_EVENT) { + if (eventType != EventType.VIEW_EVENT && eventType != EventType.VIEW_TASK) { Log.wtf(TAG, "illegal call to getResponse , wrong event type " + eventType); return Attendees.ATTENDEE_STATUS_NONE; } diff --git a/app/src/main/java/com/android/calendar/CalendarEventModel.java b/app/src/main/java/com/android/calendar/CalendarEventModel.java index 62884d1e9e..0d40e260e2 100644 --- a/app/src/main/java/com/android/calendar/CalendarEventModel.java +++ b/app/src/main/java/com/android/calendar/CalendarEventModel.java @@ -31,6 +31,7 @@ import com.android.calendar.event.EditEventHelper; import com.android.calendar.event.EventColorCache; import com.android.calendar.event.ExtendedProperty; +import com.android.calendar.icalendar.VEvent; import com.android.calendar.settings.GeneralPreferences; import com.android.common.Rfc822Validator; diff --git a/app/src/main/java/com/android/calendar/DayFragment.java b/app/src/main/java/com/android/calendar/DayFragment.java index 439a685156..692085bf96 100644 --- a/app/src/main/java/com/android/calendar/DayFragment.java +++ b/app/src/main/java/com/android/calendar/DayFragment.java @@ -16,6 +16,7 @@ package com.android.calendar; +import android.app.Fragment; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; @@ -28,8 +29,6 @@ import android.widget.ViewSwitcher; import android.widget.ViewSwitcher.ViewFactory; -import androidx.fragment.app.Fragment; - import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; import com.android.calendarcommon2.Time; @@ -100,7 +99,7 @@ public void onCreate(Bundle icicle) { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) { View v = inflater.inflate(R.layout.day_activity, null); mViewSwitcher = (ViewSwitcher) v.findViewById(R.id.switcher); diff --git a/app/src/main/java/com/android/calendar/DayView.java b/app/src/main/java/com/android/calendar/DayView.java index 0a42cb53f2..2785afc77b 100644 --- a/app/src/main/java/com/android/calendar/DayView.java +++ b/app/src/main/java/com/android/calendar/DayView.java @@ -225,7 +225,11 @@ public void run() { @Override public void run() { if (mClickedEvent != null) { - mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, mClickedEvent.id, + long eventType = EventType.VIEW_EVENT; + if (mClickedEvent.isTask()) { + eventType = EventType.VIEW_TASK; + } + mController.sendEventRelatedEvent(this, eventType, mClickedEvent.id, mClickedEvent.startMillis, mClickedEvent.endMillis, DayView.this.getWidth() / 2, mClickedYLocation, getSelectedTimeInMillis()); @@ -956,7 +960,7 @@ private void initAccessibilityVariables() { * @return selected time in UTC milliseconds since the epoch. */ long getSelectedTimeInMillis() { - Time time = new Time(Utils.getTimeZone(mContext, mTZUpdater)); + Time time = new Time(); time.set(mBaseDate); time.setJulianDay(mSelectionDay); time.setHour(mSelectionHour); @@ -4370,7 +4374,7 @@ public boolean onTouchEvent(MotionEvent ev) { return true; default: - if (DEBUG) Log.e(TAG, "Not MotionEvent " + ev); + if (DEBUG) Log.e(TAG, "Not MotionEvent " + ev.toString()); if (mGestureDetector.onTouchEvent(ev)) { return true; } diff --git a/app/src/main/java/com/android/calendar/Event.java b/app/src/main/java/com/android/calendar/Event.java index 6a31be970d..de32d664f4 100644 --- a/app/src/main/java/com/android/calendar/Event.java +++ b/app/src/main/java/com/android/calendar/Event.java @@ -20,6 +20,7 @@ import android.content.ContentUris; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; @@ -32,22 +33,27 @@ import android.text.format.DateUtils; import android.util.Log; -import com.android.calendar.settings.GeneralPreferences; +import androidx.core.content.ContextCompat; -import org.dmfs.rfc5545.DateTime; -import org.dmfs.rfc5545.iterable.RecurrenceSet; -import org.dmfs.rfc5545.iterable.instanceiterable.RuleInstances; -import org.dmfs.rfc5545.recur.RecurrenceRule; +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract; +import com.android.calendar.settings.GeneralPreferences; +import com.android.calendarcommon2.Time; -import java.text.SimpleDateFormat; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; +import java.util.GregorianCalendar; import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; import ws.xsoh.etar.R; +import static com.android.calendar.DayView.MILLIS_PER_DAY; + // TODO: should Event be Parcelable so it can be passed via Intents? public class Event implements Cloneable { @@ -94,6 +100,30 @@ public class Event implements Cloneable { Instances.ALL_DAY + "=1 OR (" + Instances.END + "-" + Instances.BEGIN + ")>=" + DateUtils.DAY_IN_MILLIS + " AS " + DISPLAY_AS_ALLDAY, // 20 }; + + public static final String[] TASK_PROJECTION = new String[]{DmfsOpenTasksContract.Tasks.COLUMN_TITLE, // 0 + DmfsOpenTasksContract.Tasks.COLUMN_LOCATION, // 1 + DmfsOpenTasksContract.Tasks.COLUMN_IS_ALLDAY, // 2 + DmfsOpenTasksContract.Tasks.COLUMN_LIST_COLOR, // 3 + DmfsOpenTasksContract.Tasks.COLUMN_TZ, // 4 + DmfsOpenTasksContract.Tasks.COLUMN_ID, // 5 + DmfsOpenTasksContract.Tasks.COLUMN_START_DATE, // 6 + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE, // 7 + DmfsOpenTasksContract.Tasks.COLUMN_ID, // 8 + DmfsOpenTasksContract.Tasks.COLUMN_START_DATE, // 9 + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE, // 10 + DmfsOpenTasksContract.Tasks.COLUMN_START_DATE, // 11 + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE, // 12 + DmfsOpenTasksContract.Tasks.COLUMN_HAS_ALLARMS, // 13 + DmfsOpenTasksContract.Tasks.COLUMN_RRULE, // 14 + DmfsOpenTasksContract.Tasks.COLUMN_RDATE, // 15 + DmfsOpenTasksContract.Tasks.COLUMN_STATUS, // 16 + DmfsOpenTasksContract.Tasks.COLUMN_ACCOUNT_NAME, // 17 + DmfsOpenTasksContract.Tasks.COLUMN_ORGANIZER, // 18 + "0>0", // 19 + "0>0", // 20 + }; + private static final String EVENTS_WHERE = DISPLAY_AS_ALLDAY + "=0"; private static final String ALLDAY_WHERE = DISPLAY_AS_ALLDAY + "=1"; // The indices for the projection array above. @@ -120,6 +150,13 @@ public class Event implements Cloneable { private static String mNoTitleString; private static int mNoColorColor; + public static final String AND_BRACKET = " AND ("; + public static final String CLOSING_BRACKET = " )"; + public static final String AND = " AND "; + public static final String OR = " OR "; + public static final String NOT_EQUALS = " != "; + public static final String LTE = " <= "; + public static final String GTE = " >= "; public long id; public int color; @@ -153,6 +190,7 @@ public class Event implements Cloneable { public Event nextDown; private int mColumn; private int mMaxColumns; + private boolean task; public static final Event newInstance() { Event e = new Event(); @@ -172,6 +210,7 @@ public static final Event newInstance() { e.isRepeating = false; e.status = Events.STATUS_CONFIRMED; e.selfAttendeeStatus = Attendees.ATTENDEE_STATUS_NONE; + e.task = false; return e; } @@ -193,6 +232,7 @@ public static void loadEvents(Context context, ArrayList events, int star Cursor cEvents = null; Cursor cAllday = null; + Cursor cTasks = null; events.clear(); try { @@ -236,6 +276,15 @@ public static void loadEvents(Context context, ArrayList events, int star buildEventsFromCursor(events, cEvents, context, startDay, endDay); buildEventsFromCursor(events, cAllday, context, startDay, endDay); + // we use tasks as events + if (ContextCompat.checkSelfPermission(context, DmfsOpenTasksContract.TASK_READ_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + cTasks = instancesQueryForTasks(context.getContentResolver(), TASK_PROJECTION, startDay, endDay); + + buildTasksFromCursor(events, cTasks, context, startDay, endDay); + Collections.sort(events, Comparator.comparing(u -> new Date(u.getStartMillis()))); + } + } finally { if (cEvents != null) { cEvents.close(); @@ -249,6 +298,116 @@ public static void loadEvents(Context context, ArrayList events, int star } } + public static Cursor instancesQueryForTasks(ContentResolver cr, String[] projection, int startDay, int endDay) { + long startMills = getMillsFromJulian(startDay, true); + long endMills = getMillsFromJulian(endDay, false); + String taskWhere = DmfsOpenTasksContract.Tasks.COLUMN_STATUS + NOT_EQUALS + DmfsOpenTasksContract.Tasks.STATUS_COMPLETED + AND_BRACKET + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE + LTE + endMills + CLOSING_BRACKET + AND_BRACKET + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE + GTE + startMills + CLOSING_BRACKET + AND_BRACKET + DmfsOpenTasksContract.Tasks.COLUMN_VISIBLE + " = 1" + CLOSING_BRACKET + AND_BRACKET + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE + " != 0" + CLOSING_BRACKET; + + return cr.query(DmfsOpenTasksContract.Tasks.PROVIDER_URI, projection, taskWhere, null, "due ASC, title ASC"); + } + + public static long getMillsFromJulian(int day, boolean isStart) { + GregorianCalendar julianbaseCal = new GregorianCalendar(); + julianbaseCal.clear(); + julianbaseCal.set(4713, Calendar.JANUARY, 1, 12, 0, 0); + julianbaseCal.set(Calendar.ERA, GregorianCalendar.BC); + + long juliandateMillis = day * (long) (MILLIS_PER_DAY); + long mills = juliandateMillis + julianbaseCal.getTimeInMillis(); + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(mills); + if (isStart) { + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + } else { + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + } + + return calendar.getTimeInMillis(); + } + + public static void buildTasksFromCursor(ArrayList events, Cursor cTasks, Context context, int startDay, int endDay) { + if (cTasks == null || events == null) { + Log.e(TAG, "buildEventsFromCursor: null cursor or null events list!"); + return; + } + + int count = cTasks.getCount(); + + if (count == 0) { + return; + } + + Resources res = context.getResources(); + mNoTitleString = res.getString(R.string.no_title_label); + mNoColorColor = res.getColor(R.color.event_center); + cTasks.moveToPosition(-1); + + while (cTasks.moveToNext()) { + Event e = generateTaskFromCursor(cTasks, context); + if (e == null) { + continue; + } + if (e.startDay > endDay || e.endDay < startDay) { + continue; + } + events.add(e); + } + } + + private static Event generateTaskFromCursor(Cursor cTasks, Context context) { + + Event e = new Event(); + + e.id = cTasks.getLong(PROJECTION_EVENT_ID_INDEX); + e.title = cTasks.getString(PROJECTION_TITLE_INDEX); + if (e.title == null || e.title.length() == 0) { + e.title = mNoTitleString; + } + e.endMillis = cTasks.getLong(PROJECTION_END_INDEX); + + e.endTime = getEndMinutes(e.endMillis); + e.startTime = e.endTime - 30; + + e.endDay = Time.getJulianDay(e.endMillis, new Time().getGmtOffset()); + e.startDay = Time.getJulianDay(e.endMillis, new Time().getGmtOffset()); + + e.startMillis = cTasks.getLong(PROJECTION_BEGIN_INDEX); + if (e.startMillis == 0) { + + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(e.endMillis); + cal.add(Calendar.MINUTE, -30); + e.startMillis = cal.getTimeInMillis(); + } + e.allDay = cTasks.getInt(PROJECTION_ALL_DAY_INDEX) != 0; + + if (!cTasks.isNull(PROJECTION_COLOR_INDEX)) { + // Read the color from the database + e.color = Utils.getDisplayColorFromColor(context, cTasks.getInt(PROJECTION_COLOR_INDEX)); + } else { + e.color = mNoColorColor; + } + + e.status = cTasks.getInt(PROJECTION_STATUS_INDEX); + e.organizer = cTasks.getString(PROJECTION_ORGANIZER_INDEX); + if (e.organizer == null) { + e.organizer = cTasks.getString(PROJECTION_SELF_ATTENDEE_STATUS_INDEX); + } + e.task = true; + return e; + } + + public static int getEndMinutes(long mills) { + GregorianCalendar instance = new GregorianCalendar(); + instance.setGregorianChange(new Date(Long.MAX_VALUE)); + instance.setTime(new Timestamp(mills)); + return (instance.get(Calendar.HOUR_OF_DAY) * 60) + instance.get(Calendar.MINUTE); + } + /** * Performs a query to return all visible instances in the given range * that match the given selection. This is a blocking function and @@ -375,22 +534,6 @@ private static Event generateEventFromCursor(Cursor cEvents, Context context) { String rdate = cEvents.getString(PROJECTION_RDATE_INDEX); if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)) { e.isRepeating = true; - - /** We need to double check a few RRULE conditions that the Android Calendar Provider - * doesn't handle and shows duplicate events for, namely: - * - * - BYSETPOS - * - BYWEEKNO - * - * For these conditions, double check if this event really occurs on this day, if it - * doesn't, reset the endDay value to 0 so it is removed from the events list. - * - * It might make sense to check all rrule's, as there may be other broken sets, but - * the overhead is probably not worth it at this point. - **/ - if (rrule instanceof String && (rrule.contains("BYSETPOS=") || rrule.contains("BYWEEKNO="))) { - e.endDay = checkRRuleEventDate(rrule, e.startMillis, e.endDay); - } } else { e.isRepeating = false; } @@ -399,71 +542,6 @@ private static Event generateEventFromCursor(Cursor cEvents, Context context) { return e; } - /** Android's RRULE code is broken in a way the creates additional events in certain - * circumstances (though never doesn't create the actual event) so let's use another RRULE - * parser to validate if the event is real or not. - * - * In this case we're using lib-recur from https://github.com/dmfs/lib-recur through maven. - * - **/ - static int checkRRuleEventDate( String rrule, long startTime, int endDay) { - // Convert the startTime into some useable Day/Month/Year values. - Date date = new java.util.Date(startTime); - - // We'll use SimpleDateFormat to get the D/M/Y but we also need to set the timezone. - SimpleDateFormat sdf = new java.text.SimpleDateFormat(); - sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT")); - - sdf.applyPattern("yyyy"); - int startYear = Integer.parseInt(sdf.format(date)); - sdf.applyPattern("MM"); - int startMonth = Integer.parseInt(sdf.format(date)) - 1; - sdf.applyPattern("dd"); - int startDay = Integer.parseInt(sdf.format(date)); - - // Parse the recurrence rule. - RecurrenceRule rule; - try { - rule = new RecurrenceRule(rrule); - } catch (Exception e) { - // On failure, assume we match and return. - return endDay; - } - - // Use the Year/Month/Day startTime values to create a firstInstance. - DateTime firstInstance = new DateTime(startYear, startMonth, startDay); - RecurrenceSet newRecurrenceSet; - - // Wrap the recurrent set creation in a try/catch to ensure we don't run into an invalid - // rule set that lib-recur can't parse. - try { - newRecurrenceSet = new RecurrenceSet(firstInstance, new RuleInstances(rule)); - } catch (Exception e) { - return endDay; - } - - // Wrap the for loop in a try/catch to ensure we don't run into an invalid - // rule set that lib-recur can't parse. - try { - // Create the recurrence set for the rule, we're only going to look at the first one - // as it should match the firstInstance if this is a valid event from Android. - for (DateTime instance:newRecurrenceSet) { - if (!instance.equals(firstInstance)) { - // If this isn't a valid event, return 0 so it gets removed from the event list. - return 0; - } else { - // If this is a valid event, return the endDay that we were passed in with. - return endDay; - } - } - } catch (Exception e) { - return endDay; - } - - // We should never get here, but add a return just in case. - return endDay; - } - /** * Computes a position for each event. Each event is displayed * as a non-overlapping rectangle. For normal events, these rectangles @@ -731,4 +809,12 @@ public boolean drawAsAllday() { // Use >= so we'll pick up Exchange allday events return allDay || endMillis - startMillis >= DateUtils.DAY_IN_MILLIS; } + + public boolean isTask() { + return task; + } + + public void setTask(boolean task) { + this.task = task; + } } diff --git a/app/src/main/java/com/android/calendar/EventInfoActivity.java b/app/src/main/java/com/android/calendar/EventInfoActivity.java index 72bcb1e825..1bae966abf 100644 --- a/app/src/main/java/com/android/calendar/EventInfoActivity.java +++ b/app/src/main/java/com/android/calendar/EventInfoActivity.java @@ -19,6 +19,8 @@ import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; +import android.app.FragmentManager; +import android.app.FragmentTransaction; import android.content.Intent; import android.content.res.Resources; import android.database.ContentObserver; @@ -31,8 +33,6 @@ import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; import com.android.calendar.CalendarEventModel.ReminderEntry; @@ -140,14 +140,14 @@ protected void onCreate(Bundle icicle) { // Get the fragment if exists mInfoFragment = (EventInfoFragment) - getSupportFragmentManager().findFragmentById(R.id.main_frame); + getFragmentManager().findFragmentById(R.id.main_frame); // Create a new fragment if none exists if (mInfoFragment == null) { - FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction ft = fragmentManager.beginTransaction(); - mInfoFragment = new EventInfoFragment(this, mEventId, mStartMillis, mEndMillis, + mInfoFragment = new EventInfoFragment(this, intent.getData(), mStartMillis, mEndMillis, attendeeResponse, isDialog, (isDialog ? EventInfoFragment.DIALOG_WINDOW_STYLE : EventInfoFragment.FULL_WINDOW_STYLE), diff --git a/app/src/main/java/com/android/calendar/EventInfoFragment.java b/app/src/main/java/com/android/calendar/EventInfoFragment.java index 264ccf3503..77bc0fc650 100644 --- a/app/src/main/java/com/android/calendar/EventInfoFragment.java +++ b/app/src/main/java/com/android/calendar/EventInfoFragment.java @@ -28,6 +28,8 @@ import android.animation.ObjectAnimator; import android.app.Activity; import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; import android.app.Service; import android.content.ActivityNotFoundException; import android.content.ContentProviderOperation; @@ -96,13 +98,10 @@ import android.widget.TextView; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.FileProvider; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.FragmentManager; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; @@ -119,6 +118,8 @@ import com.android.calendar.icalendar.Organizer; import com.android.calendar.icalendar.VCalendar; import com.android.calendar.icalendar.VEvent; +import com.android.calendar.icalendar.VTodo; +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract; import com.android.calendar.settings.GeneralPreferences; import com.android.calendarcommon2.DateException; import com.android.calendarcommon2.Duration; @@ -136,6 +137,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -190,13 +192,26 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange Calendars.ACCOUNT_NAME, // 4 Calendars.ACCOUNT_TYPE // 5 }; + + static final String[] TASK_LIST_PROJECTION = new String[]{ + DmfsOpenTasksContract.TaskLists.COLUMN_ID, // 0 + DmfsOpenTasksContract.TaskLists.COLUMN_NAME, // 1 + DmfsOpenTasksContract.TaskLists.COLUMN_LIST_OWNER, // 2 + "0 AS canOrganizerRespond", // 3 + DmfsOpenTasksContract.TaskLists.COLUMN_ACCOUNT_NAME, // 4 + DmfsOpenTasksContract.TaskLists.COLUMN_ACCOUNT_TYPE // 5 + }; static final int CALENDARS_INDEX_DISPLAY_NAME = 1; static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2; static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3; static final int CALENDARS_INDEX_ACCOUNT_NAME = 4; static final int CALENDARS_INDEX_ACCOUNT_TYPE = 5; static final String CALENDARS_WHERE = Calendars._ID + "=?"; + + static final String TASK_LIST_WHERE = DmfsOpenTasksContract.TaskLists.COLUMN_ID + "=?"; static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?"; + + static final String TASK_LIST_DUPLICATE_NAME_WHERE = DmfsOpenTasksContract.TaskLists.COLUMN_NAME + "=?"; static final String CALENDARS_VISIBLE_WHERE = Calendars.VISIBLE + "=?"; static final String[] COLORS_PROJECTION = new String[]{ Colors._ID, // 0 @@ -217,10 +232,14 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange private static final int TOKEN_QUERY_VISIBLE_CALENDARS = 1 << 5; private static final int TOKEN_QUERY_COLORS = 1 << 6; private static final int TOKEN_QUERY_EXTENDED = 1 << 7; + private static final int TOKEN_QUERY_TASK = 3; // 1 + private static final int TOKEN_QUERY_TASK_LIST = 7; // 2 + private static final int TOKEN_QUERY_VISIBLE_TASK_LIST = 11 ; // 32 + private static final int TOKEN_QUERY_DUPLICATE_TASK_LIST = 100; // 8 private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT | TOKEN_QUERY_REMINDERS | TOKEN_QUERY_VISIBLE_CALENDARS | TOKEN_QUERY_COLORS - | TOKEN_QUERY_EXTENDED; + | TOKEN_QUERY_TASK | TOKEN_QUERY_VISIBLE_TASK_LIST | TOKEN_QUERY_DUPLICATE_TASK_LIST |TOKEN_QUERY_TASK_LIST; public static final File EXPORT_SDCARD_DIRECTORY = new File( Environment.getExternalStorageDirectory(), "CalendarEvents"); @@ -258,6 +277,36 @@ private enum ShareType { Events.AVAILABILITY, // 24 Events.ACCESS_LEVEL // 25 }; + + private static final String[] TASK_PROJECTION = new String[] { + DmfsOpenTasksContract.Tasks.COLUMN_ID, // 0 do not remove; used in DeleteEventHelper + DmfsOpenTasksContract.Tasks.COLUMN_TITLE, // 1 do not remove; used in DeleteEventHelper + DmfsOpenTasksContract.Tasks.COLUMN_RRULE, // 2 do not remove; used in DeleteEventHelper + DmfsOpenTasksContract.Tasks.COLUMN_IS_ALLDAY, // 3 do not remove; used in DeleteEventHelper + DmfsOpenTasksContract.Tasks.COLUMN_LIST_ID, // 4 do not remove; used in DeleteEventHelper + DmfsOpenTasksContract.Tasks.COLUMN_DTSTART, // 5 do not remove; used in DeleteEventHelper + DmfsOpenTasksContract.Tasks.COLUMN_SYNC_ID, // 6 do not remove; used in DeleteEventHelper + DmfsOpenTasksContract.Tasks.COLUMN_TZ, // 7 do not remove; used in DeleteEventHelper + DmfsOpenTasksContract.Tasks.COLUMN_DESCRIPTION, // 8 + DmfsOpenTasksContract.Tasks.COLUMN_LOCATION, // 9 + DmfsOpenTasksContract.Tasks.COLUMN_LIST_ACCESS_LEVEL, // 10 + DmfsOpenTasksContract.Tasks.COLUMN_LIST_COLOR, // 11 + DmfsOpenTasksContract.Tasks.COLUMN_COLOR, // 12 + DmfsOpenTasksContract.Tasks.COLUMN_STATUS, // 13 + "0 AS hasAttendeeData", // 14 + DmfsOpenTasksContract.Tasks.COLUMN_ORGANIZER, // 15 + DmfsOpenTasksContract.Tasks.COLUMN_HAS_ALLARMS, // 16 + "10 AS maxReminders", // 17 + "0 AS allowedReminders", // 18 + "null AS customAppPackage", // 19 + "null AS customAppUri", // 20 + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE, // 21 + DmfsOpenTasksContract.Tasks.COLUMN_DURATION, // 22 + DmfsOpenTasksContract.Tasks.COLUMN_ORIGINAL_INSTANCE_SYNC_ID, // 23 do not remove; used in DeleteEventHelper + "0 AS availability", // 24 + "0 AS accessLevel", // 25 + DmfsOpenTasksContract.Tasks.COLUMN_ACCOUNT_NAME, // 26 + }; private static final int EVENT_INDEX_ID = 0; private static final int EVENT_INDEX_TITLE = 1; private static final int EVENT_INDEX_RRULE = 2; @@ -283,6 +332,8 @@ private enum ShareType { private static final int EVENT_INDEX_DURATION = 22; private static final int EVENT_INDEX_AVAILABILITY = 24; private static final int EVENT_INDEX_ACCESS_LEVEL = 25; + + private static final int PROJECTION_SELF_ATTENDEE_STATUS_INDEX = 26; private static final String[] ATTENDEES_PROJECTION = new String[] { Attendees._ID, // 0 Attendees.ATTENDEE_NAME, // 1 @@ -344,6 +395,7 @@ private enum ShareType { private View mView; private Uri mUri; private long mEventId; + boolean mIsTask = false; private Cursor mEventCursor; private Cursor mAttendeesCursor; private Cursor mCalendarsCursor; @@ -457,7 +509,7 @@ public void run() { private int mY = -1; private int mMinTop; // Dialog cannot be above this location private boolean mIsTabletConfig; - private AppCompatActivity mActivity; + private Activity mActivity; private Context mContext; private final Runnable mTZUpdater = new Runnable() { @Override @@ -488,6 +540,9 @@ public EventInfoFragment(Context context, Uri uri, long startMillis, long endMil setStyle(DialogFragment.STYLE_NO_TITLE, 0); mUri = uri; + if (mUri.getAuthority().equals(DmfsOpenTasksContract.AUTHORITY)) { + mIsTask = true; + } mStartMillis = startMillis; mEndMillis = endMillis; mAttendeeResponseFromIntent = attendeeResponse; @@ -497,6 +552,11 @@ public EventInfoFragment(Context context, Uri uri, long startMillis, long endMil // This may be used to explicitly show certain reminders already known // about, such as during configuration changes. mReminders = reminders; + + // when event is a task we haven't an eventId. we get it from URI + if (mIsTask) { + mEventId = Long.parseLong(mUri.getLastPathSegment()); + } } // This is currently required by the fragment manager. @@ -610,7 +670,7 @@ public void onNothingSelected(AdapterView parent) { final Activity activity = getActivity(); mContext = activity; dynamicTheme.onCreate(activity); - mColorPickerDialog = (EventColorPickerDialog) mActivity.getSupportFragmentManager() + mColorPickerDialog = (EventColorPickerDialog) activity.getFragmentManager() .findFragmentByTag(COLOR_PICKER_DIALOG_TAG); if (mColorPickerDialog != null) { mColorPickerDialog.setOnColorSelectedListener(this); @@ -690,14 +750,14 @@ public void onDetach() { } @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - mActivity = (AppCompatActivity) context; + public void onAttach(Activity activity) { + super.onAttach(activity); + mActivity = activity; // Ensure that mIsTabletConfig is set before creating the menu. mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config); mController = CalendarController.getInstance(mActivity); mController.registerEventHandler(R.layout.event_info, this); - mEditResponseHelper = new EditResponseHelper(mActivity); + mEditResponseHelper = new EditResponseHelper(activity); mEditResponseHelper.setDismissListener( new DialogInterface.OnDismissListener() { @Override @@ -742,7 +802,7 @@ public void onDismiss(DialogInterface dialog) { mEditResponseHelper.setWhichEvents(UPDATE_ALL); mWhichEvents = mEditResponseHelper.getWhichEvents(); } - mHandler = new QueryHandler(mActivity); + mHandler = new QueryHandler(activity); if (!mIsDialog) { setHasOptionsMenu(true); } @@ -866,8 +926,13 @@ public void onAnimationEnd(Animator animation) { mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY); // start loading the data - - mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION, + int tokenQueryEvent = TOKEN_QUERY_EVENT; + String[] projetion = EVENT_PROJECTION; + if (mIsTask) { + tokenQueryEvent = TOKEN_QUERY_TASK; + projetion = TASK_PROJECTION; + } + mHandler.startQuery(tokenQueryEvent, null, mUri, projetion, null, null, null); View b = mView.findViewById(R.id.delete); @@ -914,15 +979,21 @@ public void onClick(View v) { } // Create a listener for the add reminder button - View reminderAddButton = mView.findViewById(R.id.reminder_add); - View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - addReminder(); - mUserModifiedReminders = true; - } - }; - reminderAddButton.setOnClickListener(addReminderOnClickListener); + if (!mIsTask) { + View reminderAddButton = mView.findViewById(R.id.reminder_add); + View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + addReminder(); + mUserModifiedReminders = true; + } + }; + reminderAddButton.setOnClickListener(addReminderOnClickListener); + } else { + Button reminderAddButton = (Button) mView.findViewById(R.id.reminder_add); + reminderAddButton.setText(R.string.event_info_reminders_cannot_add_label); + reminderAddButton.setEnabled(false); + } // Set reminders variables @@ -1160,56 +1231,106 @@ private void shareEvent(ShareType type) { calendar.addProperty(VCalendar.CALSCALE, "GREGORIAN"); calendar.addProperty(VCalendar.METHOD, "REQUEST"); - VEvent event = new VEvent(); - mEventCursor.moveToFirst(); - // Add event start and end datetime - if (!mAllDay) { - String eventTimeZone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE); - event.addEventStart(mStartMillis, eventTimeZone); - event.addEventEnd(mEndMillis, eventTimeZone); + String filePrefix; + if (mIsTask) { + VTodo vtodo = new VTodo(); + mEventCursor.moveToFirst(); + // Add event start and end datetime + if (!mAllDay) { + String eventTimeZone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE); + vtodo.addTodoStart(mStartMillis, eventTimeZone); + vtodo.addTodoEnd(mEndMillis, eventTimeZone); + } else { + // All-day events' start and end time are stored as UTC. + // Treat the event start and end time as being in the local time zone and convert them + // to the corresponding UTC datetime. If the UTC time is used as is, the ical recipients + // will report the wrong start and end time (+/- 1 day) for the event as they will + // convert the UTC time to their respective local time-zones + String localTimeZone = Utils.getTimeZone(mActivity, mTZUpdater); + long eventStart = IcalendarUtils.convertTimeToUtc(mStartMillis, localTimeZone); + long eventEnd = IcalendarUtils.convertTimeToUtc(mEndMillis, localTimeZone); + vtodo.addTodoStart(eventStart, "UTC"); + vtodo.addTodoEnd(eventEnd, "UTC"); + } + + vtodo.addProperty(VEvent.LOCATION, mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION)); + vtodo.addProperty(VEvent.DESCRIPTION, mEventCursor.getString(EVENT_INDEX_DESCRIPTION)); + vtodo.addProperty(VEvent.SUMMARY, mEventCursor.getString(EVENT_INDEX_TITLE)); + vtodo.addOrganizer(new Organizer(mEventOrganizerDisplayName, mEventOrganizerEmail)); + + // Add Attendees to event + for (Attendee attendee : mAcceptedAttendees) { + IcalendarUtils.addAttendeeToTodo(attendee, vtodo); + } + + for (Attendee attendee : mDeclinedAttendees) { + IcalendarUtils.addAttendeeToTodo(attendee, vtodo); + } + + for (Attendee attendee : mTentativeAttendees) { + IcalendarUtils.addAttendeeToTodo(attendee, vtodo); + } + + for (Attendee attendee : mNoResponseAttendees) { + IcalendarUtils.addAttendeeToTodo(attendee, vtodo); + } + + // Compose all of the ICalendar objects + calendar.addTodo(vtodo); + + filePrefix = vtodo.getProperty(VTodo.SUMMARY); } else { - // All-day events' start and end time are stored as UTC. - // Treat the event start and end time as being in the local time zone and convert them - // to the corresponding UTC datetime. If the UTC time is used as is, the ical recipients - // will report the wrong start and end time (+/- 1 day) for the event as they will - // convert the UTC time to their respective local time-zones - String localTimeZone = Utils.getTimeZone(mActivity, mTZUpdater); - long eventStart = IcalendarUtils.convertTimeToUtc(mStartMillis, localTimeZone); - long eventEnd = IcalendarUtils.convertTimeToUtc(mEndMillis, localTimeZone); - event.addEventStart(eventStart, "UTC"); - event.addEventEnd(eventEnd, "UTC"); - } - - event.addProperty(VEvent.LOCATION, mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION)); - event.addProperty(VEvent.DESCRIPTION, mEventCursor.getString(EVENT_INDEX_DESCRIPTION)); - event.addProperty(VEvent.SUMMARY, mEventCursor.getString(EVENT_INDEX_TITLE)); - event.addOrganizer(new Organizer(mEventOrganizerDisplayName, mEventOrganizerEmail)); - - // Add Attendees to event - for (Attendee attendee : mAcceptedAttendees) { - IcalendarUtils.addAttendeeToEvent(attendee, event); - } + VEvent event = new VEvent(); + mEventCursor.moveToFirst(); + // Add event start and end datetime + if (!mAllDay) { + String eventTimeZone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE); + event.addEventStart(mStartMillis, eventTimeZone); + event.addEventEnd(mEndMillis, eventTimeZone); + } else { + // All-day events' start and end time are stored as UTC. + // Treat the event start and end time as being in the local time zone and convert them + // to the corresponding UTC datetime. If the UTC time is used as is, the ical recipients + // will report the wrong start and end time (+/- 1 day) for the event as they will + // convert the UTC time to their respective local time-zones + String localTimeZone = Utils.getTimeZone(mActivity, mTZUpdater); + long eventStart = IcalendarUtils.convertTimeToUtc(mStartMillis, localTimeZone); + long eventEnd = IcalendarUtils.convertTimeToUtc(mEndMillis, localTimeZone); + event.addEventStart(eventStart, "UTC"); + event.addEventEnd(eventEnd, "UTC"); + } - for (Attendee attendee : mDeclinedAttendees) { - IcalendarUtils.addAttendeeToEvent(attendee, event); - } + event.addProperty(VEvent.LOCATION, mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION)); + event.addProperty(VEvent.DESCRIPTION, mEventCursor.getString(EVENT_INDEX_DESCRIPTION)); + event.addProperty(VEvent.SUMMARY, mEventCursor.getString(EVENT_INDEX_TITLE)); + event.addOrganizer(new Organizer(mEventOrganizerDisplayName, mEventOrganizerEmail)); - for (Attendee attendee : mTentativeAttendees) { - IcalendarUtils.addAttendeeToEvent(attendee, event); - } + // Add Attendees to event + for (Attendee attendee : mAcceptedAttendees) { + IcalendarUtils.addAttendeeToEvent(attendee, event); + } - for (Attendee attendee : mNoResponseAttendees) { - IcalendarUtils.addAttendeeToEvent(attendee, event); - } + for (Attendee attendee : mDeclinedAttendees) { + IcalendarUtils.addAttendeeToEvent(attendee, event); + } - // Compose all of the ICalendar objects - calendar.addEvent(event); + for (Attendee attendee : mTentativeAttendees) { + IcalendarUtils.addAttendeeToEvent(attendee, event); + } + + for (Attendee attendee : mNoResponseAttendees) { + IcalendarUtils.addAttendeeToEvent(attendee, event); + } + + // Compose all of the ICalendar objects + calendar.addEvent(event); + filePrefix = event.getProperty(VEvent.SUMMARY); + } // Create and share ics file boolean isShareSuccessful = false; try { // Event title serves as the file name prefix - String filePrefix = event.getProperty(VEvent.SUMMARY); if (filePrefix == null || filePrefix.length() < 3) { // Default to a generic filename if event title doesn't qualify // Prefix length constraint is imposed by File#createTempFile @@ -1328,7 +1449,7 @@ private void showEventColorPickerDialog() { mCalendarColor, mIsTabletConfig); mColorPickerDialog.setOnColorSelectedListener(this); } - final FragmentManager fragmentManager = getParentFragmentManager(); + final FragmentManager fragmentManager = getFragmentManager(); fragmentManager.executePendingTransactions(); if (!mColorPickerDialog.isAdded()) { mColorPickerDialog.show(fragmentManager, COLOR_PICKER_DIALOG_TAG); @@ -1830,7 +1951,7 @@ private void addFieldToAccessibilityEvent(List text, TextView tv, } } - private void updateCalendar(View view) { + private void updateCalendar(View view, boolean isTaskList) { mCalendarOwnerAccount = ""; if (mCalendarsCursor != null && mEventCursor != null) { @@ -1841,10 +1962,18 @@ private void updateCalendar(View view) { mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME); // start visible calendars query - mHandler.startQuery(TOKEN_QUERY_VISIBLE_CALENDARS, null, Calendars.CONTENT_URI, - CALENDARS_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[] {"1"}, null); + if (isTaskList) { + mHandler.startQuery(TOKEN_QUERY_VISIBLE_TASK_LIST, null, DmfsOpenTasksContract.TaskLists.PROVIDER_URI, + TASK_LIST_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[]{"1"}, null); + } else { + mHandler.startQuery(TOKEN_QUERY_VISIBLE_CALENDARS, null, Calendars.CONTENT_URI, + CALENDARS_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[]{"1"}, null); + } mEventOrganizerEmail = mEventCursor.getString(EVENT_INDEX_ORGANIZER); + if (mEventOrganizerEmail == null) { + mEventOrganizerEmail = mEventCursor.getString(PROJECTION_SELF_ATTENDEE_STATUS_INDEX); + } mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(mEventOrganizerEmail); if (!TextUtils.isEmpty(mEventOrganizerEmail) && @@ -2088,7 +2217,7 @@ void updateResponse(View view) { // for simplicity). // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel. - if (!mCanModifyCalendar || !mHasAttendeeData || (mIsOrganizer && mNumOfAttendees <= 1) || + if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) || (mIsOrganizer && !mOwnerCanRespond)) { setVisibilityCommon(view, R.id.response_container, View.GONE); return; @@ -2443,9 +2572,76 @@ protected void onQueryComplete(int token, Object cookie, Cursor cursor) { startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION, CALENDARS_WHERE, args, null); break; + case TOKEN_QUERY_TASK: + mEventCursor = Utils.matrixCursorFromCursor(cursor); + if (!initEventCursor()) { + displayEventNotFound(); + return; + } + if (!mCalendarColorInitialized) { + mCalendarColor = Utils.getDisplayColorFromColor(activity, + mEventCursor.getInt(EVENT_INDEX_CALENDAR_COLOR)); + mCalendarColorInitialized = true; + } + + if (!mOriginalColorInitialized) { + mOriginalColor = mEventCursor.isNull(EVENT_INDEX_EVENT_COLOR) + ? mCalendarColor : Utils.getDisplayColorFromColor(activity, + mEventCursor.getInt(EVENT_INDEX_EVENT_COLOR)); + mOriginalColorInitialized = true; + } + + if (!mCurrentColorInitialized) { + mCurrentColor = mOriginalColor; + mCurrentColorInitialized = true; + } + + updateEvent(mView); + prepareReminders(); + + uri = DmfsOpenTasksContract.TaskLists.PROVIDER_URI; + args = new String[]{ + Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))}; + startQuery(TOKEN_QUERY_TASK_LIST, null, uri, TASK_LIST_PROJECTION, + TASK_LIST_WHERE, args, null); + + break; case TOKEN_QUERY_CALENDARS: mCalendarsCursor = Utils.matrixCursorFromCursor(cursor); - updateCalendar(mView); + updateCalendar(mView, false); + // FRAG_TODO fragments shouldn't set the title anymore + updateTitle(); + + args = new String[]{ + mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME), + mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE)}; + uri = Colors.CONTENT_URI; + startQuery(TOKEN_QUERY_COLORS, null, uri, COLORS_PROJECTION, COLORS_WHERE, args, + null); + + if (!mIsBusyFreeCalendar) { + args = new String[]{Long.toString(mEventId)}; + + // start attendees query + uri = Attendees.CONTENT_URI; + startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION, + ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER); + } else { + sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES); + } + if (mHasAlarm) { + // start reminders query + args = new String[]{Long.toString(mEventId)}; + uri = Reminders.CONTENT_URI; + startQuery(TOKEN_QUERY_REMINDERS, null, uri, + REMINDERS_PROJECTION, REMINDERS_WHERE, args, null); + } else { + sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS); + } + break; + case TOKEN_QUERY_TASK_LIST: + mCalendarsCursor = Utils.matrixCursorFromCursor(cursor); + updateCalendar(mView, true); // FRAG_TODO fragments shouldn't set the title anymore updateTitle(); @@ -2543,6 +2739,21 @@ protected void onQueryComplete(int token, Object cookie, Cursor cursor) { mCurrentQuery |= TOKEN_QUERY_DUPLICATE_CALENDARS; } break; + case TOKEN_QUERY_VISIBLE_TASK_LIST: + if (cursor.getCount() > 1) { + // Start duplicate calendars query to detect whether to add the calendar + // email to the calendar owner display. + String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); + mHandler.startQuery(TOKEN_QUERY_DUPLICATE_TASK_LIST, null, + DmfsOpenTasksContract.TaskLists.PROVIDER_URI, TASK_LIST_PROJECTION, + TASK_LIST_DUPLICATE_NAME_WHERE, new String[]{displayName}, null); + } else { + // Don't need to display the calendar owner when there is only a single + // calendar. Skip the duplicate calendars query. + setVisibilityCommon(mView, R.id.calendar_container, View.GONE); + mCurrentQuery |= TOKEN_QUERY_DUPLICATE_TASK_LIST; + } + break; case TOKEN_QUERY_DUPLICATE_CALENDARS: SpannableStringBuilder sb = new SpannableStringBuilder(); @@ -2561,6 +2772,24 @@ protected void onQueryComplete(int token, Object cookie, Cursor cursor) { setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE); mCalendarName.setText(sb); break; + case TOKEN_QUERY_DUPLICATE_TASK_LIST: + sb = new SpannableStringBuilder(); + + // Calendar display name + calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); + sb.append(calendarName); + + // Show email account if display name is not unique and + // display name != email + email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); + if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) && + Utils.isValidEmail(email)) { + sb.append(" (").append(email).append(")"); + } + + setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE); + setTextCommon(mView, R.id.calendar_name, sb); + break; } cursor.close(); sendAccessibilityEventIfQueryDone(token); diff --git a/app/src/main/java/com/android/calendar/GoogleCalendarUriIntentFilter.java b/app/src/main/java/com/android/calendar/GoogleCalendarUriIntentFilter.java index 68d9bd1cef..d2e8e6d051 100644 --- a/app/src/main/java/com/android/calendar/GoogleCalendarUriIntentFilter.java +++ b/app/src/main/java/com/android/calendar/GoogleCalendarUriIntentFilter.java @@ -199,7 +199,7 @@ protected void onCreate(Bundle icicle) { continue; } } catch (DateException e) { - if (debug) Log.d(TAG, "duration:" + e); + if (debug) Log.d(TAG, "duration:" + e.toString()); continue; } } diff --git a/app/src/main/java/com/android/calendar/ImportActivity.java b/app/src/main/java/com/android/calendar/ImportActivity.java index fa89b44579..cd76ac867e 100644 --- a/app/src/main/java/com/android/calendar/ImportActivity.java +++ b/app/src/main/java/com/android/calendar/ImportActivity.java @@ -181,13 +181,9 @@ private void parseCalFile() { String dtEnd = firstEvent.getProperty(VEvent.DTEND); String dtEndParam = firstEvent.getPropertyParameters(VEvent.DTEND); - if (dtEnd != null && !TextUtils.isEmpty(dtEnd)) { + if (!TextUtils.isEmpty(dtEnd)) { calIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, getLocalTimeFromString(dtEnd, dtEndParam)); - } else { - // Treat start date as end date if un-specified - dtEnd = dtStart; - dtEndParam = dtStartParam; } boolean isAllDay = getLocalTimeFromString(dtEnd, dtEndParam) diff --git a/app/src/main/java/com/android/calendar/SearchActivity.java b/app/src/main/java/com/android/calendar/SearchActivity.java index 78d9450e5d..4243bad206 100644 --- a/app/src/main/java/com/android/calendar/SearchActivity.java +++ b/app/src/main/java/com/android/calendar/SearchActivity.java @@ -18,6 +18,8 @@ import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; +import android.app.FragmentManager; +import android.app.FragmentTransaction; import android.app.SearchManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -38,8 +40,6 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SearchView; import androidx.core.view.MenuItemCompat; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; @@ -165,7 +165,7 @@ protected void onDestroy() { } private void initFragments(long timeMillis, String query) { - FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction ft = fragmentManager.beginTransaction(); AgendaFragment searchResultsFragment = new AgendaFragment(timeMillis, true); @@ -180,7 +180,7 @@ private void initFragments(long timeMillis, String query) { private void showEventInfo(EventInfo event) { if (mShowEventDetailsWithAgenda) { - FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction ft = fragmentManager.beginTransaction(); mEventInfoFragment = new EventInfoFragment(this, event.id, @@ -230,7 +230,7 @@ private void deleteEvent(long eventId, long startMillis, long endMillis) { mDeleteEventHelper.delete(startMillis, endMillis, eventId, -1); if (mIsMultipane && mEventInfoFragment != null && eventId == mCurrentEventId) { - FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction ft = fragmentManager.beginTransaction(); ft.remove(mEventInfoFragment); ft.commit(); diff --git a/app/src/main/java/com/android/calendar/Utils.java b/app/src/main/java/com/android/calendar/Utils.java index 119f4ddff1..135f9c81f3 100644 --- a/app/src/main/java/com/android/calendar/Utils.java +++ b/app/src/main/java/com/android/calendar/Utils.java @@ -21,7 +21,6 @@ import android.Manifest; import android.accounts.Account; import android.app.Activity; -import android.app.AlarmManager; import android.app.PendingIntent; import android.app.SearchManager; import android.content.BroadcastReceiver; @@ -55,7 +54,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import androidx.appcompat.widget.SearchView; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; @@ -207,26 +205,6 @@ public class Utils { private static boolean mAllowWeekForDetailView = false; private static String sVersion = null; - @RequiresApi(api = Build.VERSION_CODES.S) - public static boolean canScheduleAlarms(Context context) { - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - return alarmManager.canScheduleExactAlarms(); - } - - /** - * Returns whether the SDK is the UpsideDownCake release or later. - */ - public static boolean isUpsideDownCakeOrLater() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE; - } - - /** - * Returns whether the SDK is the Q release or later. - */ - public static boolean isQOrLater() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; - } - /** * Returns whether the SDK is the Oreo release or later. */ @@ -234,13 +212,6 @@ public static boolean isOreoOrLater() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; } - /** - * Returns whether the SDK is the Marshmallow release or later. - */ - public static boolean isMOrLater() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; - } - /** * Returns whether the system supports Material You. * @@ -822,9 +793,8 @@ public static long convertAlldayUtcToLocal(Time recycle, long utcTime, String tz } recycle.setTimezone(Time.TIMEZONE_UTC); recycle.set(utcTime); - Time target = new Time(tz); - target.set(0, 0, 0, recycle.getDay(), recycle.getMonth(), recycle.getYear()); - return target.normalize(); + recycle.setTimezone(tz); + return recycle.normalize(); } public static long convertAlldayLocalToUTC(Time recycle, long localTime, String tz) { @@ -833,9 +803,8 @@ public static long convertAlldayLocalToUTC(Time recycle, long localTime, String } recycle.setTimezone(tz); recycle.set(localTime); - Time target = new Time(Time.TIMEZONE_UTC); - target.set(0, 0, 0, recycle.getDay(), recycle.getMonth(), recycle.getYear()); - return target.normalize(); + recycle.setTimezone(Time.TIMEZONE_UTC); + return recycle.normalize(); } /** @@ -1182,7 +1151,7 @@ public static HashMap createDNAStrands(int firstJulianDay, segments.add(i + 1, rhs); strands.get(rhs.color).count++; if (DEBUG) { - Log.d(TAG, "Added rhs, curr:" + currSegment + " i:" + Log.d(TAG, "Added rhs, curr:" + currSegment.toString() + " i:" + segments.get(i).toString()); } } @@ -1201,7 +1170,7 @@ public static HashMap createDNAStrands(int firstJulianDay, segments.add(i++, lhs); strands.get(lhs.color).count++; if (DEBUG) { - Log.d(TAG, "Added lhs, curr:" + currSegment + " i:" + Log.d(TAG, "Added lhs, curr:" + currSegment.toString() + " i:" + segments.get(i).toString()); } } @@ -1346,7 +1315,7 @@ private static int getPixelOffsetFromMinutes(int minute, int workDayHeight, private static void addNewSegment(LinkedList segments, Event event, HashMap strands, int firstJulianDay, int minStart, int minMinutes) { if (event.startDay > event.endDay) { - Log.wtf(TAG, "Event starts after it ends: " + event); + Log.wtf(TAG, "Event starts after it ends: " + event.toString()); } // If this is a multiday event split it up by day if (event.startDay != event.endDay) { @@ -1787,7 +1756,7 @@ public static BroadcastReceiver setTimeChangesReceiver(Context c, Runnable callb filter.addAction(Intent.ACTION_LOCALE_CHANGED); CalendarBroadcastReceiver r = new CalendarBroadcastReceiver(callback); - ContextCompat.registerReceiver(c, r, filter, ContextCompat.RECEIVER_NOT_EXPORTED); + c.registerReceiver(r, filter); return r; } @@ -1985,7 +1954,7 @@ public static Spannable extendedLinkify(String text, boolean lastDitchGeo) { dialBuilder.append(ch); } } - URLSpan span = new URLSpan("tel:" + dialBuilder); + URLSpan span = new URLSpan("tel:" + dialBuilder.toString()); spanText.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); phoneCount++; @@ -2275,18 +2244,4 @@ public static boolean isCalendarPermissionGranted(Context context, boolean showW } } - /** - * Change a Time object to be the same (year, month, day, hour, minute, second) tuple - * but in another timezone - * - * @param t The Time object to modify - * @param timezone the new timezone - */ - public static void changeTimezoneOnly(Time t, String timezone) { - Time pivot = new Time(timezone); - pivot.set(t.getSecond(), t.getMinute(), t.getHour(), - t.getDay(), t.getMonth(), t.getYear()); - t.set(pivot); - } - } diff --git a/app/src/main/java/com/android/calendar/agenda/AgendaAdapter.java b/app/src/main/java/com/android/calendar/agenda/AgendaAdapter.java index 331c3d7368..ee230e3610 100644 --- a/app/src/main/java/com/android/calendar/agenda/AgendaAdapter.java +++ b/app/src/main/java/com/android/calendar/agenda/AgendaAdapter.java @@ -36,6 +36,7 @@ import com.android.calendar.Utils; import com.android.calendarcommon2.Time; +import java.util.Calendar; import java.util.Formatter; import java.util.Locale; import java.util.TimeZone; @@ -178,6 +179,13 @@ public void bindView(View view, Context context, Cursor cursor) { // When long begin = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); long end = cursor.getLong(AgendaWindowAdapter.INDEX_END); + String type = cursor.getString(AgendaWindowAdapter.INDEX_TYPE); + if (type.equals("task")) { + Calendar instance = Calendar.getInstance(); + instance.setTimeInMillis(end); + instance.add(Calendar.MINUTE, -30); + begin = instance.getTimeInMillis(); + } String eventTz = cursor.getString(AgendaWindowAdapter.INDEX_TIME_ZONE); int flags = 0; String whenString; diff --git a/app/src/main/java/com/android/calendar/agenda/AgendaByDayAdapter.java b/app/src/main/java/com/android/calendar/agenda/AgendaByDayAdapter.java index ef339bb36f..ddcbedde8b 100644 --- a/app/src/main/java/com/android/calendar/agenda/AgendaByDayAdapter.java +++ b/app/src/main/java/com/android/calendar/agenda/AgendaByDayAdapter.java @@ -33,6 +33,7 @@ import com.android.calendarcommon2.Time; import java.util.ArrayList; +import java.util.Calendar; import java.util.Formatter; import java.util.Iterator; import java.util.LinkedList; @@ -299,10 +300,25 @@ public void calculateDays(DayAdapterInfo dayAdapterInfo) { LinkedList multipleDayList = new LinkedList(); for (int position = 0; cursor.moveToNext(); position++) { - int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY); + int startDay; + int endDay; + long startTime; + long endTime; + if (dayAdapterInfo.isTask) { + long endMills = cursor.getLong(AgendaWindowAdapter.INDEX_END); + startDay = Time.getJulianDay(endMills, new Time().getGmtOffset()); + endDay = Time.getJulianDay(endMills, new Time().getGmtOffset()); + endTime = endMills; + Calendar instance = Calendar.getInstance(); + instance.add(Calendar.MINUTE, -30); + startTime = instance.getTimeInMillis(); + } else { + startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY); + endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY); + startTime = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); + endTime = cursor.getLong(AgendaWindowAdapter.INDEX_END); + } long id = cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID); - long startTime = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); - long endTime = cursor.getLong(AgendaWindowAdapter.INDEX_END); long instanceId = cursor.getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID); boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; if (allDay) { @@ -365,10 +381,6 @@ public void calculateDays(DayAdapterInfo dayAdapterInfo) { prevStartDay = startDay; } - // If this event spans multiple days, then add it to the multipleDay - // list. - int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY); - // Skip over the days outside of the adapter's range endDay = Math.min(endDay, dayAdapterInfo.end); if (endDay > startDay) { diff --git a/app/src/main/java/com/android/calendar/agenda/AgendaFragment.java b/app/src/main/java/com/android/calendar/agenda/AgendaFragment.java index 0b1091d3a6..1f0677e37b 100644 --- a/app/src/main/java/com/android/calendar/agenda/AgendaFragment.java +++ b/app/src/main/java/com/android/calendar/agenda/AgendaFragment.java @@ -17,6 +17,9 @@ package com.android.calendar.agenda; import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; import android.content.SharedPreferences; import android.os.Bundle; import android.provider.CalendarContract.Attendees; @@ -29,10 +32,6 @@ import android.widget.Adapter; import android.widget.HeaderViewListAdapter; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - import com.android.calendar.CalendarController; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; @@ -83,6 +82,8 @@ public void run() { private long mLastHandledEventId = -1; private Time mLastHandledEventTime = null; + public boolean isTask = false; + public AgendaFragment() { this(0, false); } @@ -128,7 +129,7 @@ public void onCreate(Bundle icicle) { if (prevTime != -1) { mTime.set(prevTime); if (DEBUG) { - Log.d(TAG, "Restoring time to " + mTime); + Log.d(TAG, "Restoring time to " + mTime.toString()); } } } @@ -166,10 +167,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, lv.setAdapter(a); if (a instanceof HeaderViewListAdapter) { mAdapter = (AgendaWindowAdapter) ((HeaderViewListAdapter)a).getWrappedAdapter(); + mAdapter.isTask = isTask; lv.setIndexer(mAdapter); lv.setHeaderHeightListener(mAdapter); } else if (a instanceof AgendaWindowAdapter) { mAdapter = (AgendaWindowAdapter)a; + mAdapter.isTask = isTask; lv.setIndexer(mAdapter); lv.setHeaderHeightListener(mAdapter); } else { @@ -214,6 +217,13 @@ public void onResume() { getActivity()); boolean hideDeclined = prefs.getBoolean( GeneralPreferences.KEY_HIDE_DECLINED, false); + AgendaWindowAdapter.AgendaItem item = mAgendaListView.getFirstVisibleAgendaItem(); + if (item!=null && item.isTask) { + Time t = new Time(mTimeZone); + t.set(item.end); + mController.setTime(item.end); + mTime.set(t.toMillis()); + } mAgendaListView.setHideDeclinedEvents(hideDeclined); if (mLastHandledEventId != -1) { @@ -388,7 +398,7 @@ private void showEventInfo(EventInfo event, boolean allDay, boolean replaceFragm // Create a fragment to show the event to the side of the agenda list if (mShowEventDetailsWithAgenda) { - FragmentManager fragmentManager = getParentFragmentManager(); + FragmentManager fragmentManager = getFragmentManager(); if (fragmentManager == null) { // Got a goto event before the fragment finished attaching, // stash the event and handle it later. @@ -457,6 +467,7 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun Time t = new Time(mTimeZone); t.setJulianDay(mJulianDayOnTop); mController.setTime(t.toMillis()); + mTime.set(t.toMillis()); // Cannot sent a message that eventually may change the layout of the views // so instead post a runnable that will run when the layout is done if (!mIsTabletConfig) { diff --git a/app/src/main/java/com/android/calendar/agenda/AgendaListView.java b/app/src/main/java/com/android/calendar/agenda/AgendaListView.java index cac2cce019..180ce77bdd 100644 --- a/app/src/main/java/com/android/calendar/agenda/AgendaListView.java +++ b/app/src/main/java/com/android/calendar/agenda/AgendaListView.java @@ -37,6 +37,8 @@ import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo; import com.android.calendarcommon2.Time; +import java.util.Calendar; + import ws.xsoh.etar.R; public class AgendaListView extends ListView implements OnItemClickListener { @@ -182,6 +184,13 @@ public void onItemClick(AdapterView a, View v, int position, long id) { !mShowEventDetailsWithAgenda)) { long startTime = item.begin; long endTime = item.end; + // task has startTime = 0. so we recalculate as -30 muntes from end time for reference + if (item.isTask) { + Calendar instance = Calendar.getInstance(); + instance.setTimeInMillis(endTime); + instance.add(Calendar.MINUTE, -30); + startTime = instance.getTimeInMillis(); + } // Holder in view holds the start of the specific part of a multi-day event , // use it for the goto long holderStartTime; @@ -197,7 +206,11 @@ public void onItemClick(AdapterView a, View v, int position, long id) { } mTime.set(startTime); CalendarController controller = CalendarController.getInstance(mContext); - controller.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, item.id, + long eventType = EventType.VIEW_EVENT; + if (item.isTask) { + eventType = EventType.VIEW_TASK; + } + controller.sendEventRelatedEventWithExtra(this, eventType, item.id, startTime, endTime, 0, 0, CalendarController.EventInfo.buildViewExtraLong( Attendees.ATTENDEE_STATUS_NONE, item.allDay), holderStartTime); } @@ -281,7 +294,7 @@ public long getFirstVisibleTime(AgendaItem item) { t.setSecond(second); if (DEBUG) { t.normalize(); - Log.d(TAG, "first position had time " + t); + Log.d(TAG, "first position had time " + t.toString()); } return t.normalize(); } diff --git a/app/src/main/java/com/android/calendar/agenda/AgendaWindowAdapter.java b/app/src/main/java/com/android/calendar/agenda/AgendaWindowAdapter.java index 97bd4d792c..afe4ce11f7 100644 --- a/app/src/main/java/com/android/calendar/agenda/AgendaWindowAdapter.java +++ b/app/src/main/java/com/android/calendar/agenda/AgendaWindowAdapter.java @@ -43,8 +43,10 @@ import com.android.calendar.CalendarController; import com.android.calendar.CalendarController.EventType; import com.android.calendar.CalendarController.ViewType; +import com.android.calendar.Event; import com.android.calendar.StickyHeaderListView; import com.android.calendar.Utils; +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract; import com.android.calendarcommon2.Time; import java.util.Date; @@ -93,6 +95,7 @@ public class AgendaWindowAdapter extends BaseAdapter public static final int INDEX_OWNER_ACCOUNT = 15; public static final int INDEX_CAN_ORGANIZER_RESPOND= 16; public static final int INDEX_TIME_ZONE = 17; + public static final int INDEX_TYPE = 18; static final boolean BASICLOG = false; static final boolean DEBUGLOG = false; private static final String TAG = "AgendaWindowAdapter"; @@ -119,6 +122,29 @@ public class AgendaWindowAdapter extends BaseAdapter Instances.OWNER_ACCOUNT, // 15 Instances.CAN_ORGANIZER_RESPOND, // 16 Instances.EVENT_TIMEZONE, // 17 + "'event' as type" + }; + + public static final String[] TASK_PROJECTION = new String[]{ + DmfsOpenTasksContract.Tasks.COLUMN_ID, // 0 + DmfsOpenTasksContract.Tasks.COLUMN_TITLE, // 1 + DmfsOpenTasksContract.Tasks.COLUMN_LOCATION, // 2 + DmfsOpenTasksContract.Tasks.COLUMN_IS_ALLDAY, // 3 + DmfsOpenTasksContract.Tasks.COLUMN_HAS_ALLARMS, // 4 + DmfsOpenTasksContract.Tasks.COLUMN_LIST_COLOR, // 5 + DmfsOpenTasksContract.Tasks.COLUMN_RRULE, // 6 + DmfsOpenTasksContract.Tasks.COLUMN_START_DATE, // 7 + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE, // 8 + DmfsOpenTasksContract.Tasks.COLUMN_ID, // 9 + DmfsOpenTasksContract.Tasks.COLUMN_START_DATE, // 10 + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE, // 11 + DmfsOpenTasksContract.Tasks.COLUMN_STATUS, // 12 + "0>0", // 13 + DmfsOpenTasksContract.Tasks.COLUMN_ACCOUNT_NAME, // 14 + DmfsOpenTasksContract.Tasks.COLUMN_ACCOUNT_NAME, // 15 + "0>0", // 16 + DmfsOpenTasksContract.Tasks.COLUMN_TZ, // 17 + "'task' as type" // task }; // Listview may have a bug where the index/position is not consistent when there's a header. // position == positionInListView - OFF_BY_ONE_BUG @@ -211,6 +237,8 @@ public void run() { private long mSelectedInstanceId = -1; private AgendaAdapter.ViewHolder mSelectedVH = null; + public boolean isTask = false; + public AgendaWindowAdapter(Context context, AgendaListView agendaListView, boolean showEventOnStart) { mContext = context; @@ -505,6 +533,7 @@ public AgendaItem getAgendaItemByPosition(final int positionInListView, if (cursorPosition < info.cursor.getCount()) { AgendaItem item = buildAgendaItemFromCursor(info.cursor, cursorPosition, isDayHeader); + item.isTask = info.isTask; if (!returnEventStartDay && !isDayHeader) { item.startDay = info.dayAdapter.findJulianDayFromPosition(positionInAdapter - info.offset); @@ -567,8 +596,12 @@ private void sendViewEvent(AgendaItem item, long selectedTime) { if (DEBUGLOG) { Log.d(TAG, "Sent (AgendaWindowAdapter): VIEW EVENT: " + new Date(startTime)); } + long eventType = EventType.VIEW_EVENT; + if (item.isTask) { + eventType = EventType.VIEW_TASK; + } CalendarController.getInstance(mContext) - .sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, + .sendEventRelatedEventWithExtra(this, eventType, item.id, startTime, endTime, 0, 0, CalendarController.EventInfo.buildViewExtraLong( Attendees.ATTENDEE_STATUS_NONE, @@ -710,6 +743,19 @@ private String buildQuerySelection() { } } + private String buildQuerySelectionForTasks(QuerySpec queryData) { + long startMills = Event.getMillsFromJulian(queryData.start, true); + long endMills = Event.getMillsFromJulian(queryData.end, false); + + return DmfsOpenTasksContract.Tasks.COLUMN_STATUS + Event.NOT_EQUALS + + DmfsOpenTasksContract.Tasks.STATUS_COMPLETED + Event.AND_BRACKET + + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE + Event.LTE + endMills + + Event.CLOSING_BRACKET + Event.AND_BRACKET + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE + + Event.GTE + startMills + Event.CLOSING_BRACKET + Event.AND_BRACKET + + DmfsOpenTasksContract.Tasks.COLUMN_VISIBLE + " = 1" + Event.CLOSING_BRACKET + + Event.AND_BRACKET + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE + " != 0" + Event.CLOSING_BRACKET; + } + private Uri buildQueryUri(int start, int end, String searchQuery) { Uri rootUri = searchQuery == null ? Instances.CONTENT_BY_DAY_URI : @@ -817,18 +863,26 @@ private void doQuery(QuerySpec queryData) { time.setJulianDay(queryData.start); Time time2 = new Time(mTimeZone); time2.setJulianDay(queryData.end); - Log.v(TAG, "startQuery: " + time + " to " - + time2 + " then go to " + queryData.goToTime); + Log.v(TAG, "startQuery: " + time.toString() + " to " + + time2.toString() + " then go to " + queryData.goToTime); } mQueryHandler.cancelOperation(0); if (BASICLOG) queryData.queryStartMillis = System.nanoTime(); - Uri queryUri = buildQueryUri( - queryData.start, queryData.end, queryData.searchQuery); - mQueryHandler.startQuery(0, queryData, queryUri, - PROJECTION, buildQuerySelection(), null, - AGENDA_SORT_ORDER); + //query tasks + if (isTask) { + queryData.isTask = true; + mQueryHandler.startQuery(1, queryData, DmfsOpenTasksContract.Tasks.PROVIDER_URI, + TASK_PROJECTION, buildQuerySelectionForTasks(queryData), + null, "due ASC, title ASC"); + } else { + Uri queryUri = buildQueryUri( + queryData.start, queryData.end, queryData.searchQuery); + mQueryHandler.startQuery(0, queryData, queryUri, + PROJECTION, buildQuerySelection(), null, + AGENDA_SORT_ORDER); + } } private String formatDateString(int julianDay) { @@ -969,6 +1023,8 @@ private static class QuerySpec { int queryType; long id; + boolean isTask = false; + public QuerySpec(int queryType) { this.queryType = queryType; id = -1; @@ -1031,6 +1087,8 @@ static class AgendaItem { long id; int startDay; boolean allDay; + + boolean isTask; } static class DayAdapterInfo { @@ -1041,6 +1099,8 @@ static class DayAdapterInfo { int offset; // offset in position in the list view int size; // dayAdapter.getCount() + boolean isTask = false; + public DayAdapterInfo(Context context) { dayAdapter = new AgendaByDayAdapter(context); } @@ -1053,10 +1113,10 @@ public String toString() { StringBuilder sb = new StringBuilder(); time.setJulianDay(start); time.normalize(); - sb.append("Start:").append(time); + sb.append("Start:").append(time.toString()); time.setJulianDay(end); time.normalize(); - sb.append(" End:").append(time); + sb.append(" End:").append(time.toString()); sb.append(" Offset:").append(offset); sb.append(" Size:").append(size); return sb.toString(); @@ -1191,6 +1251,7 @@ protected void onQueryComplete(int token, Object cookie, Cursor cursor) { if (tempCursor != null) { AgendaItem item = buildAgendaItemFromCursor(tempCursor, tempCursorPosition, false); + item.isTask = data.isTask; long selectedTime = findStartTimeFromPosition(newPosition); if (DEBUGLOG) { Log.d(TAG, "onQueryComplete: Sending View Event..."); @@ -1358,6 +1419,7 @@ private int processNewCursor(QuerySpec data, Cursor cursor) { } // Setup adapter info + info.isTask = data.isTask; info.start = data.start; info.end = data.end; info.cursor = cursor; diff --git a/app/src/main/java/com/android/calendar/alerts/AlertReceiver.java b/app/src/main/java/com/android/calendar/alerts/AlertReceiver.java index 2af76a4dea..39eea23af9 100644 --- a/app/src/main/java/com/android/calendar/alerts/AlertReceiver.java +++ b/app/src/main/java/com/android/calendar/alerts/AlertReceiver.java @@ -17,6 +17,8 @@ package com.android.calendar.alerts; +import static com.android.calendar.alerts.AlertService.ALERT_CHANNEL_ID; + import android.app.Notification; import android.app.PendingIntent; import android.app.Service; @@ -47,8 +49,6 @@ import android.util.Log; import android.widget.Toast; -import androidx.annotation.Nullable; - import com.android.calendar.DynamicTheme; import com.android.calendar.Utils; import com.android.calendar.alerts.AlertService.NotificationWrapper; @@ -132,21 +132,14 @@ public static void beginStartingService(Context context, Intent intent) { } mStartingService.acquire(); - if (Utils.isMOrLater()) { - if (pm.isIgnoringBatteryOptimizations(context.getPackageName())) { - if (Utils.isOreoOrLater()) { - if (Utils.isUpsideDownCakeOrLater() && !Utils.canScheduleAlarms(context)) { - return; - } - context.startForegroundService(intent); - } else { - context.startService(intent); - } + if (pm.isIgnoringBatteryOptimizations(context.getPackageName())) { + if (Utils.isOreoOrLater()) { + context.startForegroundService(intent); } else { - Log.d(TAG, "Battery optimizations are not disabled"); + context.startService(intent); } } else { - context.startService(intent); + Log.d(TAG, "Battery optimizations are not disabled"); } } } @@ -199,16 +192,8 @@ private static PendingIntent createDismissAlarmsIntent(Context context, long eve return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | Utils.PI_FLAG_IMMUTABLE); } - // if default snooze minute < 0, means the snooze option is disable - // in this case return null as intent - @Nullable private static PendingIntent createSnoozeIntent(Context context, long eventId, long startMillis, long endMillis, int notificationId) { - - if (Utils.getDefaultSnoozeDelayMs(context) < 0L) { - return null; - } - Intent intent = new Intent(); intent.putExtra(AlertUtils.EVENT_ID_KEY, eventId); intent.putExtra(AlertUtils.EVENT_START_KEY, startMillis); @@ -238,10 +223,10 @@ private static PendingIntent createAlertActivityIntent(Context context) { } public static NotificationWrapper makeBasicNotification(Context context, String title, - String summaryText, long startMillis, long endMillis, long eventId, long calendarId, + String summaryText, long startMillis, long endMillis, long eventId, int notificationId, boolean doPopup, int priority) { Notification n = buildBasicNotification(new Notification.Builder(context), - context, title, summaryText, startMillis, endMillis, eventId, calendarId, notificationId, + context, title, summaryText, startMillis, endMillis, eventId, notificationId, doPopup, priority, false); return new NotificationWrapper(n, notificationId, eventId, startMillis, endMillis, doPopup); } @@ -255,7 +240,7 @@ public static boolean isResolveIntent(Context context, Intent intent) { private static Notification buildBasicNotification(Notification.Builder notificationBuilder, Context context, String title, String summaryText, long startMillis, long endMillis, - long eventId, long calendarId, int notificationId, boolean doPopup, int priority, + long eventId, int notificationId, boolean doPopup, int priority, boolean addActionButtons) { Resources resources = context.getResources(); if (title == null || title.length() == 0) { @@ -274,7 +259,7 @@ private static Notification buildBasicNotification(Notification.Builder notifica // Create the base notification. notificationBuilder.setContentTitle(title); notificationBuilder.setContentText(summaryText); - notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar_events); + notificationBuilder.setSmallIcon(R.drawable.stat_notify_calendar); int color = DynamicTheme.getColorId(DynamicTheme.getPrimaryColor(context)); notificationBuilder.setColor(context.getResources().getColor(color)); notificationBuilder.setContentIntent(clickIntent); @@ -282,7 +267,7 @@ private static Notification buildBasicNotification(Notification.Builder notifica // Add setting channel ID for Oreo or later if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationBuilder.setChannelId(UtilsKt.channelId(calendarId)); + notificationBuilder.setChannelId(ALERT_CHANNEL_ID); } if (doPopup) { @@ -354,10 +339,10 @@ private static Notification buildBasicNotification(Notification.Builder notifica */ public static NotificationWrapper makeExpandingNotification(Context context, String title, String summaryText, String description, long startMillis, long endMillis, long eventId, - long calendarId, int notificationId, boolean doPopup, int priority) { + int notificationId, boolean doPopup, int priority) { Notification.Builder basicBuilder = new Notification.Builder(context); Notification notification = buildBasicNotification(basicBuilder, context, title, - summaryText, startMillis, endMillis, eventId, calendarId, notificationId, doPopup, + summaryText, startMillis, endMillis, eventId, notificationId, doPopup, priority, true); // Create a new-style expanded notification @@ -778,11 +763,8 @@ private static Intent createCallActivityIntent(Context context, URLSpan[] urlSpa @Override public void onReceive(final Context context, final Intent intent) { - if (context == null || intent.getAction() == null) - return; - if (AlertService.DEBUG) { - Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent); + Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString()); } if (MAP_ACTION.equals(intent.getAction())) { // Try starting the map action. @@ -855,10 +837,7 @@ public void onReceive(final Context context, final Intent intent) { } private void closeNotificationShade(Context context) { - // https://developer.android.com/about/versions/12/behavior-changes-all#close-system-dialogs - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - Intent closeNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.sendBroadcast(closeNotificationShadeIntent); - } + Intent closeNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.sendBroadcast(closeNotificationShadeIntent); } } diff --git a/app/src/main/java/com/android/calendar/alerts/AlertService.java b/app/src/main/java/com/android/calendar/alerts/AlertService.java index 1caab2322f..dad8c8421e 100644 --- a/app/src/main/java/com/android/calendar/alerts/AlertService.java +++ b/app/src/main/java/com/android/calendar/alerts/AlertService.java @@ -16,9 +16,6 @@ package com.android.calendar.alerts; -import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC; -import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED; - import android.Manifest; import android.annotation.TargetApi; import android.app.Notification; @@ -51,7 +48,6 @@ import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; -import androidx.core.app.ServiceCompat; import androidx.core.content.ContextCompat; import com.android.calendar.Utils; @@ -70,7 +66,7 @@ */ public class AlertService extends Service { - public static final String ALERT_CHANNEL_GROUP_ID = "alert_channel_group_01"; + public static final String ALERT_CHANNEL_ID = "alert_channel_01"; public static final String FOREGROUND_CHANNEL_ID = "foreground_channel_01"; // Hard limit to the number of notifications displayed. @@ -89,7 +85,6 @@ public class AlertService extends Service { CalendarAlerts.BEGIN, // 9 CalendarAlerts.END, // 10 CalendarAlerts.DESCRIPTION, // 11 - CalendarAlerts.CALENDAR_ID, // 12 }; private static final String TAG = "AlertService"; private static final int ALERT_INDEX_ID = 0; @@ -104,7 +99,6 @@ public class AlertService extends Service { private static final int ALERT_INDEX_BEGIN = 9; private static final int ALERT_INDEX_END = 10; private static final int ALERT_INDEX_DESCRIPTION = 11; - private static final int ALERT_INDEX_CALENDAR_ID = 12; private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR " + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<="; private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] { @@ -284,7 +278,7 @@ public static boolean generateAlerts(Context context, NotificationMgr nm, String summaryText = AlertUtils.formatTimeLocation(context, info.startMillis, info.allDay, info.location); notification = AlertReceiver.makeBasicNotification(context, info.eventName, - summaryText, info.startMillis, info.endMillis, info.eventId, info.calendarId, + summaryText, info.startMillis, info.endMillis, info.eventId, AlertUtils.EXPIRED_GROUP_NOTIFICATION_ID, false, Notification.PRIORITY_MIN); } else { @@ -411,7 +405,7 @@ private static void logEventIdsBumped(List list1, ids.setLength(ids.length() - 1); } if (ids.length() > 0) { - Log.d(TAG, "Reached max postings, bumping event IDs {" + ids + Log.d(TAG, "Reached max postings, bumping event IDs {" + ids.toString() + "} to digest."); } } @@ -475,7 +469,6 @@ static int processQuery(final Cursor alertCursor, final Context context, while (alertCursor.moveToNext()) { final long alertId = alertCursor.getLong(ALERT_INDEX_ID); final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID); - final long calendarId = alertCursor.getLong(ALERT_INDEX_CALENDAR_ID); final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES); final String eventName = alertCursor.getString(ALERT_INDEX_TITLE); final String description = alertCursor.getString(ALERT_INDEX_DESCRIPTION); @@ -514,7 +507,6 @@ static int processQuery(final Cursor alertCursor, final Context context, msgBuilder.append("alertCursor result: alarmTime:").append(alarmTime) .append(" alertId:").append(alertId) .append(" eventId:").append(eventId) - .append(" calendarId:").append(calendarId) .append(" state: ").append(state) .append(" minutes:").append(minutes) .append(" declined:").append(declined) @@ -594,7 +586,7 @@ static int processQuery(final Cursor alertCursor, final Context context, // TODO: Prefer accepted events in case of ties. NotificationInfo newInfo = new NotificationInfo(eventName, location, - description, beginTime, endTime, eventId, calendarId, allDay, newAlert); + description, beginTime, endTime, eventId, allDay, newAlert); // Adjust for all day events to ensure the right bucket. Don't use the 1/4 event // duration grace period for these. @@ -711,8 +703,8 @@ private static void postNotification(NotificationInfo info, String summaryText, String tickerText = getTickerText(info.eventName, info.location); NotificationWrapper notification = AlertReceiver.makeExpandingNotification(context, - info.eventName, summaryText, info.description, info.startMillis, info.endMillis, - info.eventId, info.calendarId, notificationId, prefs.getDoPopup(), priorityVal); + info.eventName, summaryText, info.description, info.startMillis, + info.endMillis, info.eventId, notificationId, prefs.getDoPopup(), priorityVal); boolean quietUpdate = true; String ringtone = NotificationPrefs.EMPTY_RINGTONE; @@ -932,23 +924,14 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { if (Utils.isOreoOrLater()) { + createChannels(this); Notification notification = new NotificationCompat.Builder(this, FOREGROUND_CHANNEL_ID) .setContentTitle(getString(R.string.foreground_notification_title)) - .setSmallIcon(R.drawable.stat_notify_refresh_events) + .setSmallIcon(R.drawable.stat_notify_calendar) .setShowWhen(false) .build(); - if (Utils.isQOrLater()) { - int serviceType; - if (Utils.isUpsideDownCakeOrLater()) { - serviceType = FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED; - } else { - serviceType = FOREGROUND_SERVICE_TYPE_DATA_SYNC; - } - ServiceCompat.startForeground(this, 1337, notification, serviceType); - } else { - startForeground(1337, notification); - } + startForeground(1337, notification); } Message msg = mServiceHandler.obtainMessage(); @@ -971,13 +954,16 @@ public IBinder onBind(Intent intent) { public static void createChannels(Context context) { if (Utils.isOreoOrLater()) { - NotificationManager nm = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + // Create notification channel + NotificationMgr nm = new NotificationMgrWrapper( + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); - // Create a channel per calendar (so that the user can turn it off with granularity) - UtilsKt.createPerCalendarChannels(context, nm); + NotificationChannel channel = new NotificationChannel( + ALERT_CHANNEL_ID, + context.getString(R.string.standalone_app_label), + NotificationManager.IMPORTANCE_HIGH); + channel.enableLights(true); - // Create a "Background tasks" channel to keep the app alive NotificationChannel foregroundChannel = new NotificationChannel( FOREGROUND_CHANNEL_ID, context.getString(R.string.foreground_notification_channel_name), @@ -985,6 +971,7 @@ public static void createChannels(Context context) { foregroundChannel.setDescription( context.getString(R.string.foreground_notification_channel_description)); + nm.createNotificationChannel(channel); nm.createNotificationChannel(foregroundChannel); } } @@ -1056,19 +1043,17 @@ static class NotificationInfo { long startMillis; long endMillis; long eventId; - long calendarId; boolean allDay; boolean newAlert; NotificationInfo(String eventName, String location, String description, long startMillis, - long endMillis, long eventId, long calendarId, boolean allDay, boolean newAlert) { + long endMillis, long eventId, boolean allDay, boolean newAlert) { this.eventName = eventName; this.location = location; this.description = description; this.startMillis = startMillis; this.endMillis = endMillis; this.eventId = eventId; - this.calendarId = calendarId; this.newAlert = newAlert; this.allDay = allDay; } diff --git a/app/src/main/java/com/android/calendar/alerts/DismissAlarmsService.java b/app/src/main/java/com/android/calendar/alerts/DismissAlarmsService.java index df92df8ef7..37ffb5969c 100644 --- a/app/src/main/java/com/android/calendar/alerts/DismissAlarmsService.java +++ b/app/src/main/java/com/android/calendar/alerts/DismissAlarmsService.java @@ -64,7 +64,7 @@ public IBinder onBind(Intent intent) { @Override public void onHandleIntent(Intent intent) { if (AlertService.DEBUG) { - Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent); + Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString()); } long eventId = intent.getLongExtra(AlertUtils.EVENT_ID_KEY, -1); diff --git a/app/src/main/java/com/android/calendar/alerts/SnoozeAlarmsService.java b/app/src/main/java/com/android/calendar/alerts/SnoozeAlarmsService.java index a72291f953..876768760a 100644 --- a/app/src/main/java/com/android/calendar/alerts/SnoozeAlarmsService.java +++ b/app/src/main/java/com/android/calendar/alerts/SnoozeAlarmsService.java @@ -33,7 +33,6 @@ import androidx.core.content.ContextCompat; import com.android.calendar.Utils; -import com.android.calendar.settings.GeneralPreferences; /** * Service for asynchronously marking a fired alarm as dismissed and scheduling @@ -94,10 +93,6 @@ public void onHandleIntent(Intent intent) { resolver.update(uri, dismissValues, selection, null); // Add a new alarm - if (snoozeDelay < 0) { - snoozeDelay = GeneralPreferences.SNOOZE_DELAY_DEFAULT_TIME * 60L * 1000L; - } - long alarmTime = System.currentTimeMillis() + snoozeDelay; ContentValues values = AlertUtils.makeContentValues(eventId, eventStart, eventEnd, alarmTime, 0); diff --git a/app/src/main/java/com/android/calendar/alerts/SnoozeDelayActivity.java b/app/src/main/java/com/android/calendar/alerts/SnoozeDelayActivity.java index dd228c4e71..41e9d2c323 100644 --- a/app/src/main/java/com/android/calendar/alerts/SnoozeDelayActivity.java +++ b/app/src/main/java/com/android/calendar/alerts/SnoozeDelayActivity.java @@ -24,7 +24,6 @@ import android.widget.TimePicker; import com.android.calendar.Utils; -import com.android.calendar.settings.GeneralPreferences; import ws.xsoh.etar.R; @@ -56,11 +55,6 @@ protected void onPrepareDialog(int id, Dialog d) { if (id == DIALOG_DELAY) { TimePickerDialog tpd = (TimePickerDialog) d; int delayMinutes = (int) (Utils.getDefaultSnoozeDelayMs(this) / (60L * 1000L)); - - if (delayMinutes < 0) { - delayMinutes = GeneralPreferences.SNOOZE_DELAY_DEFAULT_TIME; - } - int hours = delayMinutes / 60; int minutes = delayMinutes % 60; diff --git a/app/src/main/java/com/android/calendar/alerts/Utils.kt b/app/src/main/java/com/android/calendar/alerts/Utils.kt deleted file mode 100644 index 30ba68d187..0000000000 --- a/app/src/main/java/com/android/calendar/alerts/Utils.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.android.calendar.alerts - -import android.app.NotificationChannel -import android.app.NotificationChannelGroup -import android.app.NotificationManager -import android.content.Context -import android.os.Build -import android.provider.CalendarContract -import androidx.annotation.RequiresApi -import androidx.core.database.getStringOrNull -import com.android.calendar.Utils -import com.android.calendar.alerts.AlertService.ALERT_CHANNEL_GROUP_ID -import ws.xsoh.etar.R - - -val PROJECTION = arrayOf( - CalendarContract.Calendars._ID, - CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, -) - -data class CalendarChannel(val id: Long, val displayName: String?) - -fun channelId(id: Long) = "calendar$id" - -@RequiresApi(Build.VERSION_CODES.O) -fun createPerCalendarChannels(context: Context, nm: NotificationManager) { - val calendars: MutableList = mutableListOf() - - // Make sure we have the right permissions to access the calendar list - if (!Utils.isCalendarPermissionGranted(context, false)) return - - context.contentResolver.query( - CalendarContract.Calendars.CONTENT_URI, - PROJECTION, - null, - null, - CalendarContract.Calendars.ACCOUNT_NAME - )?.use { - while (it.moveToNext()) { - val id = it.getLong(PROJECTION.indexOf(CalendarContract.Calendars._ID)) - val displayName = - it.getStringOrNull(PROJECTION.indexOf(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME)) - - calendars.add(CalendarChannel(id, displayName)) - } - } - - // Make NotificationChannel group for calendars - nm.createNotificationChannelGroup( - NotificationChannelGroup( - ALERT_CHANNEL_GROUP_ID, context.getString(R.string.calendars) - ) - ) - - // Fetch list of existing notification channels - val toDelete = nm.notificationChannels.filter { channel: NotificationChannel -> - // Only consider the channels of the calendar group - channel.group == ALERT_CHANNEL_GROUP_ID - // And only keep those that don't correspond to calendars (so those we want to delete) - && !calendars.any { channelId(it.id) == channel.id } - } - - // We want to delete these channels because they don't correspond to any calendars (anymore) - toDelete.forEach { nm.deleteNotificationChannel(it.id) } - - val channels = calendars.map { - NotificationChannel( - channelId(it.id), - if (it.displayName.isNullOrBlank()) context.getString(R.string.preferences_calendar_no_display_name) else it.displayName, - NotificationManager.IMPORTANCE_HIGH - ).apply { - enableLights(true) - group = ALERT_CHANNEL_GROUP_ID - } - } - - channels.forEach { nm.createNotificationChannel(it) } -} diff --git a/app/src/main/java/com/android/calendar/event/CreateEventDialogFragment.java b/app/src/main/java/com/android/calendar/event/CreateEventDialogFragment.java index 4303262018..1922918939 100644 --- a/app/src/main/java/com/android/calendar/event/CreateEventDialogFragment.java +++ b/app/src/main/java/com/android/calendar/event/CreateEventDialogFragment.java @@ -18,6 +18,7 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -25,6 +26,7 @@ import android.os.Bundle; import android.provider.CalendarContract; import android.provider.CalendarContract.Calendars; +import android.provider.Settings; import android.text.Editable; import android.text.TextWatcher; import android.text.format.DateUtils; @@ -36,15 +38,12 @@ import android.widget.TextView; import android.widget.Toast; -import androidx.fragment.app.DialogFragment; - import com.android.calendar.AsyncQueryService; import com.android.calendar.CalendarController; import com.android.calendar.CalendarController.EventType; import com.android.calendar.CalendarEventModel; import com.android.calendar.Utils; import com.android.calendar.settings.GeneralPreferences; -import com.android.calendar.settings.SettingsActivity; import com.android.calendarcommon2.Time; import java.text.ParseException; @@ -238,10 +237,18 @@ private void setDefaultCalendarView(Cursor cursor) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.no_syncable_calendars).setIconAttribute( android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found) - .setPositiveButton(R.string.add_calendar, (dialog, which) -> { - if (activity != null) { - Intent nextIntent = new Intent(activity, SettingsActivity.class); - activity.startActivity(nextIntent); + .setPositiveButton(R.string.add_account, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + if (activity != null) { + Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT); + final String[] array = {"com.android.calendar"}; + nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array); + nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | + Intent.FLAG_ACTIVITY_NEW_TASK); + activity.startActivity(nextIntent); + } } }) .setNegativeButton(android.R.string.no, null); diff --git a/app/src/main/java/com/android/calendar/event/EditEventActivity.java b/app/src/main/java/com/android/calendar/event/EditEventActivity.java index 7fe0a582b2..c1a9edc60e 100644 --- a/app/src/main/java/com/android/calendar/event/EditEventActivity.java +++ b/app/src/main/java/com/android/calendar/event/EditEventActivity.java @@ -20,6 +20,7 @@ import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; +import android.app.FragmentTransaction; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -28,7 +29,6 @@ import android.view.MenuItem; import androidx.appcompat.app.ActionBar; -import androidx.fragment.app.FragmentTransaction; import com.android.calendar.AbstractCalendarActivity; import com.android.calendar.CalendarController; @@ -77,7 +77,7 @@ protected void onCreate(Bundle icicle) { setContentView(binding.getRoot()); setSupportActionBar(binding.include.toolbar); - mEditFragment = (EditEventFragment) getSupportFragmentManager().findFragmentById(R.id.body_frame); + mEditFragment = (EditEventFragment) getFragmentManager().findFragmentById(R.id.body_frame); mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config); @@ -109,7 +109,7 @@ protected void onCreate(Bundle icicle) { mEditFragment.mShowModifyDialogOnLaunch = getIntent().getBooleanExtra( CalendarController.EVENT_EDIT_ON_LAUNCH, false); - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.body_frame, mEditFragment); ft.show(mEditFragment); ft.commit(); @@ -172,19 +172,9 @@ private EventInfo getEventInfoFromIntent(Bundle icicle) { @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { - onBackPressed(); + Utils.returnToCalendarHome(this); return true; } return super.onOptionsItemSelected(item); } - - @Override - public void onBackPressed() { - if (mEditFragment != null) { - mEditFragment.onBackPressed(); - return; - } - - super.onBackPressed(); - } } diff --git a/app/src/main/java/com/android/calendar/event/EditEventFragment.java b/app/src/main/java/com/android/calendar/event/EditEventFragment.java index d226abb531..a0ad30938b 100644 --- a/app/src/main/java/com/android/calendar/event/EditEventFragment.java +++ b/app/src/main/java/com/android/calendar/event/EditEventFragment.java @@ -22,6 +22,8 @@ import android.Manifest; import android.app.Activity; import android.app.AlertDialog; +import android.app.Fragment; +import android.app.FragmentManager; import android.content.AsyncQueryHandler; import android.content.ContentProviderOperation; import android.content.ContentResolver; @@ -60,8 +62,6 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; import com.android.calendar.AsyncQueryService; import com.android.calendar.CalendarController; @@ -162,7 +162,7 @@ public void onClick(View v) { mColorPickerDialog.setCalendarColor(mModel.getCalendarColor()); mColorPickerDialog.setColors(colors, mModel.getEventColor()); } - final FragmentManager fragmentManager = getParentFragmentManager(); + final FragmentManager fragmentManager = getFragmentManager(); fragmentManager.executePendingTransactions(); if (!mColorPickerDialog.isAdded()) { mColorPickerDialog.show(fragmentManager, COLOR_PICKER_DIALOG_TAG); @@ -212,7 +212,7 @@ private void setModelIfDone(int queryType) { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mColorPickerDialog = (EventColorPickerDialog) getActivity().getSupportFragmentManager() + mColorPickerDialog = (EventColorPickerDialog) getActivity().getFragmentManager() .findFragmentByTag(COLOR_PICKER_DIALOG_TAG); if (mColorPickerDialog != null) { mColorPickerDialog.setOnColorSelectedListener(this); @@ -272,7 +272,7 @@ private void startQuery() { mModel.mCalendarAccessLevel = Calendars.CAL_ACCESS_NONE; mOutstandingQueries = TOKEN_ALL; if (DEBUG) { - Log.d(TAG, "startQuery: uri for event is " + mUri); + Log.d(TAG, "startQuery: uri for event is " + mUri.toString()); } mHandler.startQuery(TOKEN_EVENT, null, mUri, EditEventHelper.EVENT_PROJECTION, null /* selection */, null /* selection args */, null /* sort order */); @@ -559,37 +559,19 @@ boolean isEmptyNewEvent() { return mModel.isEmpty(); } - public void onBackPressed() { - if (canSave()) { - showDiscardConfirmAlert(); - return; - } - - Utils.returnToCalendarHome(getActivity()); - } - - private boolean canSave() { + @Override + public void onPause() { Activity act = getActivity(); - return mSaveOnDetach && act != null && !mIsReadOnly && !act.isChangingConfigurations() - && mView.prepareForSave(); - } - - private void showDiscardConfirmAlert() { - new AlertDialog.Builder(getActivity()) - .setMessage(R.string.discard_event_changes) - .setCancelable(true) - .setPositiveButton(R.string.discard, ((dialog, which) -> { - revertEventChanges(); - Utils.returnToCalendarHome(getActivity()); - dialog.cancel(); - })) - .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.cancel())) - .show(); - } - - private void revertEventChanges() { - mOnDone.setDoneCode(Utils.DONE_REVERT); + if (mSaveOnDetach && act != null && !mIsReadOnly && !act.isChangingConfigurations() + && mView.prepareForSave()) { + mOnDone.setDoneCode(Utils.DONE_SAVE); mOnDone.run(); + } + if (act !=null && (Build.VERSION.SDK_INT < 23 || + ContextCompat.checkSelfPermission(EditEventFragment.this.getActivity(), + Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED)) + act.finish(); + super.onPause(); } @Override diff --git a/app/src/main/java/com/android/calendar/event/EditEventHelper.java b/app/src/main/java/com/android/calendar/event/EditEventHelper.java index b7126a8d80..39fdf576a1 100644 --- a/app/src/main/java/com/android/calendar/event/EditEventHelper.java +++ b/app/src/main/java/com/android/calendar/event/EditEventHelper.java @@ -23,6 +23,7 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.provider.CalendarContract; import android.provider.CalendarContract.Attendees; import android.provider.CalendarContract.Calendars; import android.provider.CalendarContract.Colors; diff --git a/app/src/main/java/com/android/calendar/event/EditEventView.java b/app/src/main/java/com/android/calendar/event/EditEventView.java index 8c83296846..cce2478c93 100644 --- a/app/src/main/java/com/android/calendar/event/EditEventView.java +++ b/app/src/main/java/com/android/calendar/event/EditEventView.java @@ -16,8 +16,10 @@ package com.android.calendar.event; +import android.app.Activity; import android.app.AlertDialog; import android.app.DatePickerDialog; +import android.app.FragmentManager; import android.app.ProgressDialog; import android.app.Service; import android.app.TimePickerDialog; @@ -34,6 +36,7 @@ import android.provider.CalendarContract.Calendars; import android.provider.CalendarContract.Events; import android.provider.CalendarContract.Reminders; +import android.provider.Settings; import android.text.InputFilter; import android.text.TextUtils; import android.text.format.DateFormat; @@ -64,10 +67,8 @@ import android.widget.TextView.OnEditorActionListener; import android.widget.TimePicker; -import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SwitchCompat; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.fragment.app.FragmentManager; import com.android.calendar.CalendarEventModel; import com.android.calendar.CalendarEventModel.Attendee; @@ -80,7 +81,6 @@ import com.android.calendar.event.EditEventHelper.EditDoneRunnable; import com.android.calendar.recurrencepicker.RecurrencePickerDialog; import com.android.calendar.settings.GeneralPreferences; -import com.android.calendar.settings.SettingsActivity; import com.android.calendarcommon2.EventRecurrence; import com.android.calendarcommon2.Time; import com.android.common.Rfc822InputFilter; @@ -99,8 +99,10 @@ import java.util.Arrays; import java.util.Formatter; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.TimeZone; +import java.util.stream.Collectors; import ws.xsoh.etar.R; @@ -172,7 +174,7 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa private ProgressDialog mLoadingCalendarsDialog; private AlertDialog mNoCalendarsDialog; - private AppCompatActivity mActivity; + private Activity mActivity; private EditDoneRunnable mDone; private View mView; private CalendarEventModel mModel; @@ -222,7 +224,7 @@ public class EditEventView implements View.OnClickListener, DialogInterface.OnCa private ArrayList mUnsupportedReminders = new ArrayList(); private String mRrule; - public EditEventView(AppCompatActivity activity, View view, EditDoneRunnable done) { + public EditEventView(Activity activity, View view, EditDoneRunnable done) { mActivity = activity; mView = view; @@ -357,7 +359,7 @@ public void onNothingSelected(AdapterView arg0) { // Display loading screen setModel(null); - FragmentManager fm = activity.getSupportFragmentManager(); + FragmentManager fm = activity.getFragmentManager(); RecurrencePickerDialog rpd = (RecurrencePickerDialog) fm .findFragmentByTag(FRAG_TAG_RECUR_PICKER); if (rpd != null) { @@ -421,12 +423,12 @@ public void onTimeZoneSet(TimeZoneInfo tzi) { private void setTimezone(String timeZone) { mTimezone = timeZone; - Utils.changeTimezoneOnly(mStartTime, mTimezone); - Utils.changeTimezoneOnly(mEndTime, mTimezone); - long startMillis = mStartTime.normalize(); + mStartTime.setTimezone(mTimezone); + long timeMillis = mStartTime.normalize(); + mEndTime.setTimezone(mTimezone); mEndTime.normalize(); - populateTimezone(startMillis); + populateTimezone(timeMillis); } private void populateTimezone(long eventStartTime) { @@ -445,7 +447,7 @@ private void showTimezoneDialog() { b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, mStartTime.toMillis()); b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, mTimezone); - FragmentManager fm = mActivity.getSupportFragmentManager(); + FragmentManager fm = mActivity.getFragmentManager(); TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER); if (tzpd != null) { @@ -539,7 +541,7 @@ public void onClick(View view) { // TODO may be more efficient to serialize and pass in EventRecurrence b.putString(RecurrencePickerDialog.BUNDLE_RRULE, mRrule); - FragmentManager fm = mActivity.getSupportFragmentManager(); + FragmentManager fm = mActivity.getFragmentManager(); RecurrencePickerDialog rpd = (RecurrencePickerDialog) fm .findFragmentByTag(FRAG_TAG_RECUR_PICKER); if (rpd != null) { @@ -592,7 +594,10 @@ public void onClick(DialogInterface dialog, int which) { mDone.setDoneCode(Utils.DONE_REVERT); mDone.run(); if (which == DialogInterface.BUTTON_POSITIVE) { - Intent nextIntent = new Intent(mActivity, SettingsActivity.class); + Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT); + final String[] array = {"com.android.calendar"}; + nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array); + nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); mActivity.startActivity(nextIntent); } } @@ -656,54 +661,36 @@ private boolean fillModelFromUI() { } if (mModel.mAllDay) { - // Reset start and end time without touching date; - // in model, increment the monthDay by 1, and set + // Reset start and end time, increment the monthDay by 1, and set // the timezone to UTC, as required for all-day events. + mTimezone = Time.TIMEZONE_UTC; mStartTime.setHour(0); mStartTime.setMinute(0); mStartTime.setSecond(0); - mStartTime.normalize(); - Time modelStartTime = new Time(Time.TIMEZONE_UTC); - modelStartTime.set(0, 0, 0, mStartTime.getDay(), mStartTime.getMonth(), mStartTime.getYear()); - mModel.mStart = modelStartTime.normalize(); + mStartTime.setTimezone(mTimezone); + mModel.mStart = mStartTime.normalize(); mEndTime.setHour(0); mEndTime.setMinute(0); mEndTime.setSecond(0); - mEndTime.normalize(); - Time modelEndTime = new Time(Time.TIMEZONE_UTC); - modelEndTime.set(0, 0, 0, mEndTime.getDay(), mEndTime.getMonth(), mEndTime.getYear()); - // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), model's end time - // should be Y + 1 (Oct.30), but display end time should be Y (Oct. 29). + mEndTime.setTimezone(mTimezone); + // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time + // should be Y + 1 (Oct.30). final long normalizedEndTimeMillis = - modelEndTime.normalize() + DateUtils.DAY_IN_MILLIS; + mEndTime.normalize() + DateUtils.DAY_IN_MILLIS; if (normalizedEndTimeMillis < mModel.mStart) { - // mModel.mEnd should be midnight of the next day of mStart - // but mEndTime same day as mStart + // mEnd should be midnight of the next day of mStart. mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS; - modelEndTime.set(mModel.mStart); - // cannot set to mModel.mStart because mEndTime is not necessarily in the same timezone, - // so midnight of same day is not same absolute time point in millis - mEndTime.set(0, 0, 0, modelStartTime.getDay(), modelStartTime.getMonth(), modelStartTime.getYear()); - mEndTime.normalize(); } else { mModel.mEnd = normalizedEndTimeMillis; } - - mModel.mTimezone = Time.TIMEZONE_UTC; - - // refresh UI to new start & end times - setDate(mStartDateButton, mStartTime.toMillis()); - setTime(mStartTimeButton, mStartTime.toMillis()); - setDate(mEndDateButton, mEndTime.toMillis()); - setTime(mEndTimeButton, mEndTime.toMillis()); } else { mStartTime.setTimezone(mTimezone); mEndTime.setTimezone(mTimezone); mModel.mStart = mStartTime.toMillis(); mModel.mEnd = mEndTime.toMillis(); - mModel.mTimezone = mTimezone; } + mModel.mTimezone = mTimezone; mModel.mAccessLevel = mAccessLevelSpinner.getSelectedItemPosition(); // TODO set correct availability value mModel.mAvailability = mAvailabilityValues.get(mAvailabilitySpinner @@ -845,22 +832,20 @@ public void setModel(CalendarEventModel model) { boolean canRespond = EditEventHelper.canRespond(model); - { - long begin = model.mStart; - long end = model.mEnd; - mTimezone = model.mTimezone; // this will be UTC for all day events + long begin = model.mStart; + long end = model.mEnd; + mTimezone = model.mTimezone; // this will be UTC for all day events - // Set up the starting times - if (begin > 0) { - mStartTime.setTimezone(mTimezone); - mStartTime.set(begin); - mStartTime.normalize(); - } - if (end > 0) { - mEndTime.setTimezone(mTimezone); - mEndTime.set(end); - mEndTime.normalize(); - } + // Set up the starting times + if (begin > 0) { + mStartTime.setTimezone(mTimezone); + mStartTime.set(begin); + mStartTime.normalize(); + } + if (end > 0) { + mEndTime.setTimezone(mTimezone); + mEndTime.set(end); + mEndTime.normalize(); } mRrule = model.mRrule; @@ -882,9 +867,6 @@ public void setModel(CalendarEventModel model) { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { setAllDayViewsVisibility(isChecked); - if(!isChecked) { - resetToDefaultDuration(); - } } }); @@ -893,37 +875,10 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (model.mAllDay) { mAllDayCheckBox.setChecked(true); // put things back in local time for all day events - // and force time at midnight - // also be robust against model having non-normalised all day event - // (start or end not midnight UTC or timezone not UTC), and force that mTimezone = Utils.getTimeZone(mActivity, null); - { - int year = mStartTime.getYear(); - int month = mStartTime.getMonth(); - int day = mStartTime.getDay(); - mStartTime.setTimezone(Time.TIMEZONE_UTC); - mStartTime.set(0, 0, 0, day, month, year); - model.mStart = mStartTime.normalize(); - mStartTime.setTimezone(mTimezone); - mStartTime.set(0, 0, 0, day, month, year); - mStartTime.normalize(); - } - { - int year = mEndTime.getYear(); - int month = mEndTime.getMonth(); - int day = mEndTime.getDay(); - mEndTime.setTimezone(Time.TIMEZONE_UTC); - mEndTime.set(0, 0, 0, day, month, year); - model.mEnd = mEndTime.normalize(); - mEndTime.setTimezone(mTimezone); - mEndTime.set(0, 0, 0, day, month, year); - mEndTime.normalize(); - } - // refresh UI to new start & end times - setDate(mStartDateButton, mStartTime.toMillis()); - setTime(mStartTimeButton, mStartTime.toMillis()); - setDate(mEndDateButton, mEndTime.toMillis()); - setTime(mEndTimeButton, mEndTime.toMillis()); + mStartTime.setTimezone(mTimezone); + mEndTime.setTimezone(mTimezone); + mEndTime.normalize(); } else { mAllDayCheckBox.setChecked(false); } @@ -1143,7 +1098,7 @@ public void setCalendarsCursor(Cursor cursor, boolean userVisible, long selected AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); builder.setTitle(R.string.no_syncable_calendars).setIconAttribute( android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found) - .setPositiveButton(R.string.add_calendar, this) + .setPositiveButton(R.string.add_account, this) .setNegativeButton(android.R.string.no, this).setOnCancelListener(this); mNoCalendarsDialog = builder.show(); return; @@ -1433,15 +1388,6 @@ private void setTime(TextView view, long millis) { view.setText(timeString); } - protected void resetToDefaultDuration() { - mEndTime.setDay(mEndTime.getDay() - 1); - mEndTime.set(mStartTime.normalize() + - Utils.getDefaultEventDurationInMillis(mActivity)); - long endMillis = mEndTime.normalize(); - setDate(mEndDateButton, endMillis); - setTime(mEndTimeButton, endMillis); - } - /** * @param isChecked */ diff --git a/app/src/main/java/com/android/calendar/event/EventViewUtils.java b/app/src/main/java/com/android/calendar/event/EventViewUtils.java index 997a569757..52642bc09f 100644 --- a/app/src/main/java/com/android/calendar/event/EventViewUtils.java +++ b/app/src/main/java/com/android/calendar/event/EventViewUtils.java @@ -46,24 +46,11 @@ private EventViewUtils() { // if the given minutes is 63, then this returns the string "63 minutes". // As another example, if the given minutes is 120, then this returns // "2 hours". - // if minute is < 0, returns `None` public static String constructReminderLabel(Context context, int minutes, boolean abbrev) { Resources resources = context.getResources(); - - if (minutes < 0) { - return resources.getString(R.string.no_snooze_label); - } - int value, resId; - if (minutes == Integer.MIN_VALUE) { - value = 0; - resId = R.string.no_reminder_label; - - String format = resources.getString(resId, value); - return String.format(format, value); - - } else if (minutes % 60 != 0 || minutes == 0) { + if (minutes % 60 != 0 || minutes == 0) { value = minutes; if (abbrev) { resId = R.plurals.Nmins; diff --git a/app/src/main/java/com/android/calendar/event/ExtendedProperty.java b/app/src/main/java/com/android/calendar/event/ExtendedProperty.java index 118a89a796..a2c7e00e21 100644 --- a/app/src/main/java/com/android/calendar/event/ExtendedProperty.java +++ b/app/src/main/java/com/android/calendar/event/ExtendedProperty.java @@ -1,6 +1,7 @@ package com.android.calendar.event; import android.content.ContentResolver; +import android.content.ContentUris; import android.net.Uri; import android.provider.CalendarContract; import android.provider.CalendarContract.ExtendedProperties; diff --git a/app/src/main/java/com/android/calendar/icalendar/IcalendarUtils.java b/app/src/main/java/com/android/calendar/icalendar/IcalendarUtils.java index 8473cb3859..75ad6d7c2e 100644 --- a/app/src/main/java/com/android/calendar/icalendar/IcalendarUtils.java +++ b/app/src/main/java/com/android/calendar/icalendar/IcalendarUtils.java @@ -276,6 +276,33 @@ public static void addAttendeeToEvent(CalendarEventModel.Attendee attendee, VEve event.addAttendee(vAttendee); } + public static void addAttendeeToTodo(CalendarEventModel.Attendee attendee, VTodo vTodo) { + if (attendee == null || vTodo == null) return; + Attendee vAttendee = new Attendee(); + vAttendee.addProperty(Attendee.CN, attendee.mName); + + String participationStatus; + switch (attendee.mStatus) { + case CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED: + participationStatus = "ACCEPTED"; + break; + case CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED: + participationStatus = "DECLINED"; + break; + case CalendarContract.Attendees.ATTENDEE_STATUS_TENTATIVE: + participationStatus = "TENTATIVE"; + break; + case CalendarContract.Attendees.ATTENDEE_STATUS_NONE: + default: + participationStatus = "NEEDS-ACTION"; + break; + } + vAttendee.addProperty(Attendee.PARTSTAT, participationStatus); + vAttendee.mEmail = attendee.mEmail; + + vTodo.addAttendee(vAttendee); + } + /** * Returns an iCalendar formatted UTC date-time * ex: 20141120T120000Z for noon on Nov 20, 2014 diff --git a/app/src/main/java/com/android/calendar/icalendar/VCalendar.java b/app/src/main/java/com/android/calendar/icalendar/VCalendar.java index 8799eb4357..c45ddecd73 100644 --- a/app/src/main/java/com/android/calendar/icalendar/VCalendar.java +++ b/app/src/main/java/com/android/calendar/icalendar/VCalendar.java @@ -49,6 +49,7 @@ public class VCalendar { // Stores attributes and their corresponding values belonging to the Calendar object public HashMap mProperties; public LinkedList mEvents; // Events that belong to this Calendar object + public LinkedList mTodos; // Todos that belong to this Calendar object /** * Constructor @@ -56,6 +57,7 @@ public class VCalendar { public VCalendar() { mProperties = new HashMap(); mEvents = new LinkedList(); + mTodos = new LinkedList(); } /** @@ -82,6 +84,12 @@ public void addEvent(VEvent event) { if (event != null) mEvents.add(event); } + public void addTodo(VTodo todo) { + if (todo != null) { + mTodos.add(todo); + } + } + /** * * @return @@ -90,6 +98,10 @@ public LinkedList getAllEvents() { return mEvents; } + public LinkedList getAllTodos() { + return mTodos; + } + /** * Returns the iCal representation of the calendar and all of its inherent components * @return @@ -111,6 +123,10 @@ public String getICalFormattedString() { output.append(event.getICalFormattedString()); } + for (VTodo todo : mTodos) { + output.append(todo.getICalFormattedString()); + } + output.append("END:VCALENDAR\n"); return output.toString(); @@ -129,6 +145,11 @@ public void populateFromString(ArrayList input) { VEvent event = new VEvent(); event.populateFromEntries(iter); mEvents.add(event); + } else if (line.contains("BEGIN:VTODO")) { + iter.previous(); + VTodo todo = new VTodo(); + todo.populateFromEntries(iter); + mTodos.add(todo); } else if (line.contains("END:VCALENDAR")) { break; } diff --git a/app/src/main/java/com/android/calendar/icalendar/VTodo.java b/app/src/main/java/com/android/calendar/icalendar/VTodo.java new file mode 100644 index 0000000000..dac29265af --- /dev/null +++ b/app/src/main/java/com/android/calendar/icalendar/VTodo.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2014-2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.calendar.icalendar; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.ListIterator; +import java.util.UUID; + +/** + * Models the Todo/VTodo component of the iCalendar format + */ +public class VTodo { + + // Valid property identifiers for an todo component + // TODO: only a partial list of attributes has been implemented, implement the rest + public static String CLASS = "CLASS"; + public static String CREATED = "CREATED"; + public static String LOCATION = "LOCATION"; + public static String ORGANIZER = "ORGANIZER"; + public static String PRIORITY = "PRIORITY"; + public static String SEQ = "SEQ"; + public static String STATUS = "STATUS"; + public static String UID = "UID"; + public static String URL = "URL"; + public static String DTSTART = "DTSTART"; + public static String DTEND = "DTEND"; + public static String DURATION = "DURATION"; + public static String DTSTAMP = "DTSTAMP"; + public static String SUMMARY = "SUMMARY"; + public static String DESCRIPTION = "DESCRIPTION"; + public static String ATTENDEE = "ATTENDEE"; + public static String CATEGORIES = "CATEGORIES"; + + // Stores the -arity of the attributes that this component can have + private static HashMap sPropertyList = new HashMap(); + + // Initialize the approved list of mProperties for a calendar vtodo + static { + sPropertyList.put(CLASS, 1); + sPropertyList.put(CREATED, 1); + sPropertyList.put(LOCATION, 1); + sPropertyList.put(ORGANIZER, 1); + sPropertyList.put(PRIORITY, 1); + sPropertyList.put(SEQ, 1); + sPropertyList.put(STATUS, 1); + sPropertyList.put(UID, 1); + sPropertyList.put(URL, 1); + sPropertyList.put(DTSTART, 1); + sPropertyList.put(DTEND, 1); + sPropertyList.put(DURATION, 1); + sPropertyList.put(DTSTAMP, 1); + sPropertyList.put(SUMMARY, 1); + sPropertyList.put(DESCRIPTION, 1); + + sPropertyList.put(ATTENDEE, Integer.MAX_VALUE); + sPropertyList.put(CATEGORIES, Integer.MAX_VALUE); + sPropertyList.put(CATEGORIES, Integer.MAX_VALUE); + } + + // Stores attributes and their corresponding values belonging to the VTodo component + public HashMap mProperties; + public HashMap mPropertyParameters; + + public LinkedList mAttendees; + public Organizer mOrganizer; + + /** + * Constructor + */ + public VTodo() { + mProperties = new HashMap(); + mPropertyParameters = new HashMap(); + mAttendees = new LinkedList(); + + // Generate and add a unique identifier to this todo - iCal requisite + addProperty(UID, UUID.randomUUID().toString() + "@ws.xsoh.etar"); + addTimeStamp(); + } + + /** + * For adding unary properties. For adding other property attributes , use the respective + * component methods to create and add these special components. + * + * @param property + * @param value + * @return + */ + public boolean addProperty(String property, String value) { + // Only unary-properties for now + if (sPropertyList.containsKey(property) && sPropertyList.get(property) == 1 && + value != null) { + mProperties.put(property, IcalendarUtils.cleanseString(value)); + return true; + } + return false; + } + + /** + * Returns the value of the requested vtodo property or null if there isn't one + */ + public String getProperty(String property) { + return mProperties.get(property); + } + + /** + * Returns the parameters of the requested vtodo property or null if there isn't one + */ + public String getPropertyParameters(String property) { + return mPropertyParameters.get(property); + } + + /** + * Add attendees to the vtodo + * + * @param attendee + */ + public void addAttendee(Attendee attendee) { + if (attendee != null) mAttendees.add(attendee); + } + + /** + * Add an Organizer to the vtodo + * + * @param organizer + */ + public void addOrganizer(Organizer organizer) { + if (organizer != null) mOrganizer = organizer; + } + + /** + * Add an start date-time to the vtodo + */ + public void addTodoStart(long startMillis, String timeZone) { + if (startMillis < 0) return; + + String formattedDateTime = IcalendarUtils.getICalFormattedDateTime(startMillis, timeZone); + addProperty(DTSTART, formattedDateTime); + } + + /** + * Add an end date-time for todo + */ + public void addTodoEnd(long endMillis, String timeZone) { + if (endMillis < 0) return; + + String formattedDateTime = IcalendarUtils.getICalFormattedDateTime(endMillis, timeZone); + addProperty(DTEND, formattedDateTime); + } + + /** + * Timestamps the todos with the current date-time + */ + private void addTimeStamp() { + String formattedDateTime = IcalendarUtils.getICalFormattedDateTime( + System.currentTimeMillis(), "UTC"); + addProperty(DTSTAMP, formattedDateTime); + } + + /** + * Returns the iCal representation of the Todo component + */ + public String getICalFormattedString() { + StringBuilder sb = new StringBuilder(); + + // Add Todo properties + sb.append("BEGIN:VTODO\n"); + for (String property : mProperties.keySet()) { + sb.append(property + ":" + mProperties.get(property) + "\n"); + } + + // Enforce line length requirements + sb = IcalendarUtils.enforceICalLineLength(sb); + + sb.append(mOrganizer.getICalFormattedString()); + + // Add Todo Attendees + for (Attendee attendee : mAttendees) { + sb.append(attendee.getICalFormattedString()); + } + + sb.append("END:VTODO\n"); + + return sb.toString(); + } + + public void populateFromEntries(ListIterator iter) { + while (iter.hasNext()) { + String line = iter.next(); + if (line.contains("BEGIN:VTODO")) { + // Continue + } else if (line.startsWith("END:VTODO")) { + break; + } else if (line.startsWith("ORGANIZER")) { + String entry = parseTillNextAttribute(iter, line); + mOrganizer = Organizer.populateFromICalString(entry); + } else if (line.startsWith("ATTENDEE")) { + // Go one previous, so VTodo, parses current line + iter.previous(); + + // Offload to Attendee for parsing + Attendee attendee = new Attendee(); + attendee.populateFromEntries(iter); + mAttendees.add(attendee); + } else if (line.contains(":")) { + String entry = parseTillNextAttribute(iter, line); + int indexOfFirstColon = entry.indexOf(":"); + int indexOfFirstParamDelimiter = entry.indexOf(";"); + String key; + if (indexOfFirstParamDelimiter != -1 && indexOfFirstParamDelimiter < indexOfFirstColon) { + key = entry.substring(0, indexOfFirstParamDelimiter); + String params = entry.substring(indexOfFirstParamDelimiter + 1, indexOfFirstColon); + mPropertyParameters.put(key, params); + } else { + key = entry.substring(0, indexOfFirstColon); + } + String value = entry.substring(indexOfFirstColon + 1); + mProperties.put(key, value); + } + } + } + + public static String parseTillNextAttribute(ListIterator iter, String currentLine) { + StringBuilder parse = new StringBuilder(); + parse.append(currentLine); + while (iter.hasNext()) { + String line = iter.next(); + if (line.startsWith(" ")) { + parse.append(line.replaceFirst(" ", "")); + } else { + iter.previous(); + break; + } + } + return parse.toString(); + } + +} diff --git a/app/src/main/java/com/android/calendar/month/MonthByWeekFragment.java b/app/src/main/java/com/android/calendar/month/MonthByWeekFragment.java index 7cd2a5132b..331f29319a 100644 --- a/app/src/main/java/com/android/calendar/month/MonthByWeekFragment.java +++ b/app/src/main/java/com/android/calendar/month/MonthByWeekFragment.java @@ -17,7 +17,12 @@ package com.android.calendar.month; import android.app.Activity; +import android.app.FragmentManager; +import android.app.LoaderManager; import android.content.ContentUris; +import android.content.CursorLoader; +import android.content.Loader; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; import android.graphics.drawable.StateListDrawable; @@ -39,12 +44,7 @@ import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; -import androidx.annotation.NonNull; -import androidx.fragment.app.FragmentManager; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.CursorLoader; -import androidx.loader.content.Loader; - +import androidx.core.content.ContextCompat; import com.android.calendar.CalendarController; import com.android.calendar.CalendarController.EventInfo; import com.android.calendar.CalendarController.EventType; @@ -53,10 +53,14 @@ import com.android.calendar.Event; import com.android.calendar.Utils; import com.android.calendar.event.CreateEventDialogFragment; +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract; import com.android.calendarcommon2.Time; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.HashMap; import java.util.List; @@ -137,8 +141,8 @@ public void run() { @Override public void run() { if (!mIsDetached) { - mLoader = (CursorLoader) LoaderManager.getInstance(MonthByWeekFragment.this) - .initLoader(0, null, MonthByWeekFragment.this); + mLoader = (CursorLoader) getLoaderManager().initLoader(0, null, + MonthByWeekFragment.this); } } }; @@ -146,7 +150,7 @@ public void run() { @Override public void handleMessage(Message msg) { - final FragmentManager manager = getParentFragmentManager(); + final FragmentManager manager = getFragmentManager(); if (manager != null) { Time day = (Time) msg.obj; mEventDialog = new CreateEventDialogFragment(day); @@ -174,7 +178,8 @@ public MonthByWeekFragment(long initialTime, boolean isMiniMonth) { private Uri updateUri() { SimpleWeekView child = (SimpleWeekView) mListView.getChildAt(0); if (child != null) { - mFirstLoadedJulianDay = child.getFirstJulianDay(); + int julianDay = child.getFirstJulianDay(); + mFirstLoadedJulianDay = julianDay; } // -1 to ensure we get all day events from any time zone mTempTime.setJulianDay(mFirstLoadedJulianDay - 1); @@ -311,13 +316,10 @@ public void onActivityCreated(Bundle savedInstanceState) { // To get a smoother transition when showing this fragment, delay loading of events until // the fragment is expended fully and the calendar controls are gone. - if (Utils.isCalendarPermissionGranted(mContext, true) && !mIsMiniMonth) { - if (mShowCalendarControls) { - mListView.postDelayed(mLoadingRunnable, mEventsLoadingDelay); - } else { - mLoader = (CursorLoader) LoaderManager.getInstance(MonthByWeekFragment.this) - .initLoader(0, null, this); - } + if (mShowCalendarControls) { + mListView.postDelayed(mLoadingRunnable, mEventsLoadingDelay); + } else { + mLoader = (CursorLoader) getLoaderManager().initLoader(0, null, this); } mAdapter.setListView(mListView); } @@ -337,9 +339,11 @@ protected void setUpHeader() { } // TODO - @NonNull @Override public Loader onCreateLoader(int id, Bundle args) { + if (mIsMiniMonth) { + return null; + } CursorLoader loader; synchronized (mUpdateLoader) { mFirstLoadedJulianDay = @@ -348,8 +352,11 @@ public Loader onCreateLoader(int id, Bundle args) { mEventUri = updateUri(); String where = updateWhere(); + if (!Utils.isCalendarPermissionGranted(mContext, true)) { + return null; + } loader = new CursorLoader( - requireActivity(), mEventUri, Event.EVENT_PROJECTION, where, + getActivity(), mEventUri, Event.EVENT_PROJECTION, where, null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER); loader.setUpdateThrottle(LOADER_THROTTLE_DELAY); } @@ -381,7 +388,7 @@ public void doResumeUpdates() { } @Override - public void onLoadFinished(@NonNull Loader loader, Cursor data) { + public void onLoadFinished(Loader loader, Cursor data) { synchronized (mUpdateLoader) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Found " + data.getCount() + " cursor entries for uri " + mEventUri); @@ -399,13 +406,22 @@ public void onLoadFinished(@NonNull Loader loader, Cursor data) { ArrayList events = new ArrayList(); Event.buildEventsFromCursor( events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay); + + if (ContextCompat.checkSelfPermission(mContext, DmfsOpenTasksContract.TASK_READ_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + Cursor cTasks = Event.instancesQueryForTasks(mContext.getContentResolver(), Event.TASK_PROJECTION, mFirstLoadedJulianDay, mLastLoadedJulianDay); + Event.buildTasksFromCursor(events, cTasks, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay); + + Collections.sort(events, Comparator.comparing(u -> new Date(u.getStartMillis()))); + } + ((MonthByWeekAdapter) mAdapter).setEvents(mFirstLoadedJulianDay, mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events); } } @Override - public void onLoaderReset(@NonNull Loader loader) { + public void onLoaderReset(Loader loader) { } @Override diff --git a/app/src/main/java/com/android/calendar/month/MonthListView.java b/app/src/main/java/com/android/calendar/month/MonthListView.java index 02ddf88d50..f4962e5805 100644 --- a/app/src/main/java/com/android/calendar/month/MonthListView.java +++ b/app/src/main/java/com/android/calendar/month/MonthListView.java @@ -43,14 +43,10 @@ public class MonthListView extends ListView { private static int MULTIPLE_MONTH_VELOCITY_THRESHOLD = 2000; private static int FLING_VELOCITY_DIVIDER = 500; private static int FLING_TIME = 1000; - private static int LEFT_RIGHT_DISTANCE = 150; - private static double LEFT_RIGHT_RATIO = 1.25; // disposable variable used for time calculations protected Time mTempTime; private long mDownActionTime; - private float mLeftRightActionLeft; - private float mLeftRightActionRight; private final Rect mFirstViewRect = new Rect(); Context mListContext; @@ -113,13 +109,9 @@ private boolean processEvent (MotionEvent ev) { case MotionEvent.ACTION_DOWN: mTracker.clear(); mDownActionTime = SystemClock.uptimeMillis(); - mLeftRightActionLeft = ev.getX(); - mLeftRightActionRight = ev.getY(); break; // Accumulate velocity and do a custom fling when above threshold - // and look for left/right swipes as well. case MotionEvent.ACTION_UP: - // Check for the fling. mTracker.addMovement(ev); mTracker.computeCurrentVelocity(1000); // in pixels per second float vel = mTracker.getYVelocity (); @@ -127,17 +119,6 @@ private boolean processEvent (MotionEvent ev) { doFling(vel); return true; } - // Check for the left/right swipe. - float leftRightSwipe = mLeftRightActionLeft - ev.getX(); - float leftRightRatio = Math.abs(leftRightSwipe) / Math.abs(mLeftRightActionRight - ev.getY()); - if( Math.abs(leftRightSwipe) > LEFT_RIGHT_DISTANCE && leftRightRatio > LEFT_RIGHT_RATIO ) { - if( leftRightSwipe > 0 ) { - doLeftRight(1); - } else { - doLeftRight(-1); - } - return true; - } break; default: mTracker.addMovement(ev); @@ -204,46 +185,6 @@ private void doFling(float velocityY) { smoothScrollBy(viewsToFling * firstViewHeight + offset, FLING_TIME); } - // Do a "snap to start of month" fling - // Left = +1 - // Right = -1 - private void doLeftRight(int monthsToJump) { - - // Stop the list-view movement and take over - MotionEvent cancelEvent = MotionEvent.obtain(mDownActionTime, SystemClock.uptimeMillis(), - MotionEvent.ACTION_CANCEL, 0, 0, 0); - onTouchEvent(cancelEvent); - - // Get the day at the top right corner - int day = getUpperRightJulianDay(); - // Get the day of the first day of the next/previous month - // (according to scroll direction) and make sure we're crossing - // the boundry. - mTempTime.setJulianDay(day + 6); - mTempTime.setDay(1); - mTempTime.setMonth(mTempTime.getMonth() + monthsToJump); - long timeInMillis = mTempTime.normalize(); - // Since each view is 7 days, round the target day up to make sure the - // scroll will be at least one view. - int scrollToDay = Time.getJulianDay(timeInMillis, mTempTime.getGmtOffset()) - + ((monthsToJump > 0) ? 6 : 0); - - // Since all views have the same height, scroll by pixels instead of - // "to position". - // Compensate for the top view offset from the top. - View firstView = getChildAt(0); - int firstViewHeight = firstView.getHeight(); - // Get visible part length - firstView.getLocalVisibleRect(mFirstViewRect); - int topViewVisiblePart = mFirstViewRect.bottom - mFirstViewRect.top; - int viewsToFling = (scrollToDay - day) / 7 - ((monthsToJump <= 0) ? 1 : 0); - int offset = (viewsToFling > 0) ? -(firstViewHeight - topViewVisiblePart - + SimpleDayPickerFragment.LIST_TOP_OFFSET) : (topViewVisiblePart - - SimpleDayPickerFragment.LIST_TOP_OFFSET); - // Fling - smoothScrollBy(viewsToFling * firstViewHeight + offset, FLING_TIME); - } - // Returns the julian day of the day in the upper right corner private int getUpperRightJulianDay() { SimpleWeekView child = (SimpleWeekView) getChildAt(0); diff --git a/app/src/main/java/com/android/calendar/month/SimpleDayPickerFragment.java b/app/src/main/java/com/android/calendar/month/SimpleDayPickerFragment.java index 143ef9cccd..59a6fa5768 100644 --- a/app/src/main/java/com/android/calendar/month/SimpleDayPickerFragment.java +++ b/app/src/main/java/com/android/calendar/month/SimpleDayPickerFragment.java @@ -17,6 +17,7 @@ package com.android.calendar.month; import android.app.Activity; +import android.app.ListFragment; import android.content.Context; import android.database.DataSetObserver; import android.os.Bundle; @@ -34,8 +35,6 @@ import android.widget.ListView; import android.widget.TextView; -import androidx.fragment.app.ListFragment; - import com.android.calendar.DynamicTheme; import com.android.calendar.Utils; import com.android.calendarcommon2.Time; diff --git a/app/src/main/java/com/android/calendar/month/SimpleWeekView.java b/app/src/main/java/com/android/calendar/month/SimpleWeekView.java index aab255912f..3e9376c79f 100644 --- a/app/src/main/java/com/android/calendar/month/SimpleWeekView.java +++ b/app/src/main/java/com/android/calendar/month/SimpleWeekView.java @@ -157,7 +157,7 @@ public class SimpleWeekView extends View { protected int mSelectedRight = -1; // The timezone to display times/dates in (used for determining when Today // is) - protected String mTimeZone; + protected String mTimeZone = Utils.getCurrentTimezone(); protected int mBGColor; protected int mSelectedWeekBGColor; @@ -181,8 +181,6 @@ public SimpleWeekView(Context context) { mWeekNumColor = DynamicTheme.getColor(context, "month_week_num_color"); mSelectedDayLine = res.getDrawable(R.drawable.dayline_minical_holo_light); - mTimeZone = Utils.getTimeZone(context, mTZUpdater); - if (mScale == 0) { mScale = context.getResources().getDisplayMetrics().density; if (mScale != 1) { @@ -201,14 +199,6 @@ public SimpleWeekView(Context context) { initView(); } - private final Runnable mTZUpdater = new Runnable() { - @Override - public void run() { - String tz = Utils.getTimeZone(getContext(), this); - mTimeZone = tz; - } - }; - /** * Sets all the parameters for displaying this week. The only required * parameter is the week number. Other parameters have a default value and diff --git a/app/src/main/java/com/android/calendar/persistence/Calendar.kt b/app/src/main/java/com/android/calendar/persistence/Calendar.kt index fd67f94f90..c08820126d 100644 --- a/app/src/main/java/com/android/calendar/persistence/Calendar.kt +++ b/app/src/main/java/com/android/calendar/persistence/Calendar.kt @@ -30,4 +30,5 @@ data class Calendar(val id: Long, val visible: Boolean, val syncEvents: Boolean, val isPrimary: Boolean, - val isLocal: Boolean) + val isLocal: Boolean, + val isTasks: Boolean) diff --git a/app/src/main/java/com/android/calendar/persistence/CalendarRepository.kt b/app/src/main/java/com/android/calendar/persistence/CalendarRepository.kt index 0fa3d73a3b..ea9da6a3e4 100644 --- a/app/src/main/java/com/android/calendar/persistence/CalendarRepository.kt +++ b/app/src/main/java/com/android/calendar/persistence/CalendarRepository.kt @@ -26,6 +26,7 @@ import android.content.Context import android.net.Uri import android.provider.CalendarContract import androidx.lifecycle.LiveData +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract import ws.xsoh.etar.R @@ -42,7 +43,11 @@ internal class CalendarRepository(val application: Application) { private var contentResolver = application.contentResolver - private var allCalendars: CalendarLiveData = CalendarLiveData(application.applicationContext) + private var allCalendars: CalendarLiveData + + init { + allCalendars = CalendarLiveData(application.applicationContext) + } fun getCalendarsOrderedByAccount(): LiveData> { return allCalendars @@ -66,7 +71,24 @@ internal class CalendarRepository(val application: Application) { val isPrimary = it.getInt(PROJECTION_INDEX_IS_PRIMARY) == 1 val isLocal = accountType == CalendarContract.ACCOUNT_TYPE_LOCAL - calendars.add(Calendar(id, accountName, accountType, name, displayName, color, visible, syncEvents, isPrimary, isLocal)) + calendars.add(Calendar(id, accountName, accountType, name, displayName, color, visible, syncEvents, isPrimary, isLocal, false)) + } + } + + context.contentResolver.query(taskListsUri, TASKLIST_PROJECTION, null, null, null)?.use { + while (it.moveToNext()) { + val id = it.getLong(0) + val accountName = it.getString(1) + val accountType = it.getString(2) + val name = it.getString(4) + val displayName = it.getString(4) + val color = it.getInt(5) + val visible = it.getInt(6) == 1 + val syncEvents = it.getInt(7) == 1 + val isPrimary = true + val isLocal = accountType == CalendarContract.ACCOUNT_TYPE_LOCAL + + calendars.add(Calendar(id, accountName, accountType, name, displayName, color, visible, syncEvents, isPrimary, isLocal, true)) } } return calendars @@ -74,6 +96,7 @@ internal class CalendarRepository(val application: Application) { companion object { private val uri = CalendarContract.Calendars.CONTENT_URI + private val taskListsUri = DmfsOpenTasksContract.TaskLists.PROVIDER_URI private val PROJECTION = arrayOf( CalendarContract.Calendars._ID, @@ -87,6 +110,18 @@ internal class CalendarRepository(val application: Application) { CalendarContract.Calendars.SYNC_EVENTS, CalendarContract.Calendars.IS_PRIMARY ) + + private val TASKLIST_PROJECTION = arrayOf( + DmfsOpenTasksContract.TaskLists.COLUMN_ID, + DmfsOpenTasksContract.TaskLists.COLUMN_ACCOUNT_NAME, + DmfsOpenTasksContract.TaskLists.COLUMN_ACCOUNT_TYPE, + DmfsOpenTasksContract.TaskLists.COLUMN_LIST_OWNER, + DmfsOpenTasksContract.TaskLists.COLUMN_NAME, + DmfsOpenTasksContract.TaskLists.COLUMN_COLOR, + DmfsOpenTasksContract.TaskLists.COLUMN_VISIBLE, + DmfsOpenTasksContract.TaskLists.COLUMN_SYNC_ENABLE + ) + const val PROJECTION_INDEX_ID = 0 const val PROJECTION_INDEX_ACCOUNT_NAME = 1 const val PROJECTION_INDEX_ACCOUNT_TYPE = 2 @@ -178,23 +213,43 @@ internal class CalendarRepository(val application: Application) { return contentResolver.delete(calUri, null, null) == 1 } - fun queryAccount(calendarId: Long): Account? { - val calendarUri = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calendarId) - contentResolver.query(calendarUri, ACCOUNT_PROJECTION, null, null, null)?.use { - if (it.moveToFirst()) { - val accountName = it.getString(PROJECTION_ACCOUNT_INDEX_NAME) - val accountType = it.getString(PROJECTION_ACCOUNT_INDEX_TYPE) - return Account(accountName, accountType) + fun queryAccount(calendarId: Long, isTask: Boolean): Account? { + if (isTask) { + val taskUri = + ContentUris.withAppendedId(DmfsOpenTasksContract.TaskLists.PROVIDER_URI, calendarId) + contentResolver.query(taskUri, ACCOUNT_PROJECTION, null, null, null)?.use { + if (it.moveToFirst()) { + val accountName = it.getString(PROJECTION_ACCOUNT_INDEX_NAME) + val accountType = it.getString(PROJECTION_ACCOUNT_INDEX_TYPE) + return Account(accountName, accountType) + } + } + } else { + val calendarUri = + ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calendarId) + contentResolver.query(calendarUri, ACCOUNT_PROJECTION, null, null, null)?.use { + if (it.moveToFirst()) { + val accountName = it.getString(PROJECTION_ACCOUNT_INDEX_NAME) + val accountType = it.getString(PROJECTION_ACCOUNT_INDEX_TYPE) + return Account(accountName, accountType) + } } } return null } - fun queryNumberOfEvents(calendarId: Long): Long? { + fun queryNumberOfEvents(calendarId: Long, isTask: Boolean): Long? { val args = arrayOf(calendarId.toString()) - contentResolver.query(CalendarContract.Events.CONTENT_URI, PROJECTION_COUNT_EVENTS, WHERE_COUNT_EVENTS, args, null)?.use { - if (it.moveToFirst()) { - return it.getLong(PROJECTION_COUNT_EVENTS_INDEX_COUNT) + if (isTask) { + val cursor = contentResolver.query(DmfsOpenTasksContract.Tasks.PROVIDER_URI, null, WHERE_COUNT_TASKS, args, null) + val count = cursor?.count?.toLong() + cursor?.close() + return count + } else { + contentResolver.query(CalendarContract.Events.CONTENT_URI, PROJECTION_COUNT_EVENTS, WHERE_COUNT_EVENTS, args, null)?.use { + if (it.moveToFirst()) { + return it.getLong(PROJECTION_COUNT_EVENTS_INDEX_COUNT) + } } } return null @@ -213,6 +268,7 @@ internal class CalendarRepository(val application: Application) { ) const val PROJECTION_COUNT_EVENTS_INDEX_COUNT = 0 const val WHERE_COUNT_EVENTS = CalendarContract.Events.CALENDAR_ID + "=?" + const val WHERE_COUNT_TASKS = DmfsOpenTasksContract.Tasks.COLUMN_LIST_ID + "=?" const val DEFAULT_COLOR_KEY = "1" diff --git a/app/src/main/java/com/android/calendar/persistence/tasks/DmfsOpenTasksContract.java b/app/src/main/java/com/android/calendar/persistence/tasks/DmfsOpenTasksContract.java new file mode 100644 index 0000000000..7b6a32ef31 --- /dev/null +++ b/app/src/main/java/com/android/calendar/persistence/tasks/DmfsOpenTasksContract.java @@ -0,0 +1,63 @@ +package com.android.calendar.persistence.tasks; + +import android.net.Uri; +import android.provider.BaseColumns; + +public class DmfsOpenTasksContract implements BaseColumns { + public static final String AUTHORITY = "org.dmfs.tasks"; + + public static final String TASK_READ_PERMISSION = "org.dmfs.permission.READ_TASKS"; + public static final String TASK_WRITE_PERMISSION = "org.dmfs.permission.WRITE_TASKS"; + + public static final String ACCOUNT_TYPE_LOCAL = "org.dmfs.account.LOCAL"; + + public static class Tasks { + + public static final Uri PROVIDER_URI = Uri.parse("content://" + AUTHORITY + "/tasks"); + + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_TITLE = "title"; + public static final String COLUMN_LOCATION = "location"; + public static final String COLUMN_DUE_DATE = "due"; + public static final String COLUMN_VISIBLE = "visible"; + public static final String COLUMN_IS_ALLDAY = "is_allday"; + public static final String COLUMN_HAS_ALLARMS = "has_alarms"; + public static final String COLUMN_RRULE = "rrule"; + public static final String COLUMN_RDATE = "rdate"; + public static final String COLUMN_START_DATE = "dtstart"; + public static final String COLUMN_COLOR = "task_color"; + public static final String COLUMN_TZ = "tz"; + public static final String COLUMN_STATUS = "status"; + public static final String COLUMN_ORGANIZER = "organizer"; + public static final String COLUMN_ACCOUNT_NAME = "account_name"; + public static final String COLUMN_LIST_ID = "list_id"; + + public static final String COLUMN_DTSTART = "dtstart"; + public static final String COLUMN_SYNC_ID = "_sync_id"; + + public static final String COLUMN_DESCRIPTION = "description"; + public static final String COLUMN_LIST_ACCESS_LEVEL = "list_access_level"; + public static final String COLUMN_LIST_COLOR = "list_color"; + public static final String COLUMN_DURATION = "duration"; + public static final String COLUMN_ORIGINAL_INSTANCE_SYNC_ID = "original_instance_sync_id"; + + + public static final int STATUS_COMPLETED = 2; + } + + public static class TaskLists { + + public static final Uri PROVIDER_URI = Uri.parse("content://" + AUTHORITY + "/tasklists"); + + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_NAME = "list_name"; + public static final String COLUMN_COLOR = "list_color"; + public static final String COLUMN_ACCOUNT_NAME = "account_name"; + public static final String COLUMN_ACCOUNT_TYPE = "account_type"; + public static final String COLUMN_LIST_OWNER = "list_owner"; + public static final String COLUMN_VISIBLE = "visible"; + public static final String COLUMN_SYNC_ENABLE = "sync_enabled"; + } + + public static final String PERMISSION = "org.dmfs.permission.READ_TASKS"; +} diff --git a/app/src/main/java/com/android/calendar/recurrencepicker/RecurrencePickerDialog.java b/app/src/main/java/com/android/calendar/recurrencepicker/RecurrencePickerDialog.java index 67588e28a8..171768e996 100644 --- a/app/src/main/java/com/android/calendar/recurrencepicker/RecurrencePickerDialog.java +++ b/app/src/main/java/com/android/calendar/recurrencepicker/RecurrencePickerDialog.java @@ -18,6 +18,7 @@ import android.app.Activity; import android.app.DatePickerDialog; +import android.app.DialogFragment; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -54,7 +55,6 @@ import android.widget.ToggleButton; import androidx.appcompat.widget.SwitchCompat; -import androidx.fragment.app.DialogFragment; import com.android.calendar.Utils; import com.android.calendarcommon2.EventRecurrence; @@ -412,7 +412,7 @@ static private void copyModelToEventRecurrence(final RecurrenceModel model, if (!canHandleRecurrenceRule(er)) { throw new IllegalStateException("UI generated recurrence that it can't handle. ER:" - + er + " Model: " + model); + + er.toString() + " Model: " + model.toString()); } } diff --git a/app/src/main/java/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java b/app/src/main/java/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java index 522fb1818b..ab23330925 100644 --- a/app/src/main/java/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java +++ b/app/src/main/java/com/android/calendar/selectcalendars/SelectCalendarsSimpleAdapter.java @@ -16,10 +16,13 @@ package com.android.calendar.selectcalendars; +import android.app.FragmentManager; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.provider.CalendarContract.Calendars; import android.view.LayoutInflater; import android.view.TouchDelegate; @@ -31,8 +34,6 @@ import android.widget.ListAdapter; import android.widget.TextView; -import androidx.fragment.app.FragmentManager; - import com.android.calendar.CalendarColorPickerDialog; import com.android.calendar.DynamicTheme; import com.android.calendar.Utils; diff --git a/app/src/main/java/com/android/calendar/selectcalendars/SelectVisibleCalendarsFragment.java b/app/src/main/java/com/android/calendar/selectcalendars/SelectVisibleCalendarsFragment.java index 8bfab6f2fd..2f1b391400 100644 --- a/app/src/main/java/com/android/calendar/selectcalendars/SelectVisibleCalendarsFragment.java +++ b/app/src/main/java/com/android/calendar/selectcalendars/SelectVisibleCalendarsFragment.java @@ -17,6 +17,7 @@ package com.android.calendar.selectcalendars; import android.app.Activity; +import android.app.Fragment; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; @@ -29,8 +30,6 @@ import android.widget.AdapterView; import android.widget.ListView; -import androidx.fragment.app.Fragment; - import com.android.calendar.AsyncQueryService; import com.android.calendar.CalendarController; import com.android.calendar.CalendarController.EventInfo; @@ -134,7 +133,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mAdapter = new SelectCalendarsSimpleAdapter(mContext, mCalendarItemLayout, null, - getParentFragmentManager()); + getFragmentManager()); mList.setAdapter(mAdapter); mList.setOnItemClickListener(this); } diff --git a/app/src/main/java/com/android/calendar/settings/CalendarColorPickerDialogX.kt b/app/src/main/java/com/android/calendar/settings/CalendarColorPickerDialogX.kt index 523b231114..be8c67de12 100644 --- a/app/src/main/java/com/android/calendar/settings/CalendarColorPickerDialogX.kt +++ b/app/src/main/java/com/android/calendar/settings/CalendarColorPickerDialogX.kt @@ -107,7 +107,7 @@ class CalendarColorPickerDialogX : ColorPickerDialogX() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = super.onCreateDialog(savedInstanceState) - queryService = QueryService(requireActivity()) + queryService = QueryService(activity!!) if (mColors == null) { startQuery() } @@ -194,7 +194,7 @@ class CalendarColorPickerDialogX : ColorPickerDialogX() { } private fun useDefaultColors() { - val warningDialog = AlertDialog.Builder(requireActivity()) + val warningDialog = AlertDialog.Builder(activity!!) .setTitle(R.string.preferences_calendar_color_warning_title) .setMessage(R.string.preferences_calendar_color_warning_message) .setPositiveButton(R.string.preferences_calendar_color_warning_button) { dialogInterface, _ -> diff --git a/app/src/main/java/com/android/calendar/settings/CalendarDataStore.kt b/app/src/main/java/com/android/calendar/settings/CalendarDataStore.kt index ceb46942d4..f652fdd7fb 100644 --- a/app/src/main/java/com/android/calendar/settings/CalendarDataStore.kt +++ b/app/src/main/java/com/android/calendar/settings/CalendarDataStore.kt @@ -19,17 +19,21 @@ package com.android.calendar.settings import android.content.ContentUris import android.content.ContentValues +import android.net.Uri import android.provider.CalendarContract import androidx.fragment.app.FragmentActivity import androidx.preference.PreferenceDataStore +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract /** * Custom data store for preferences that saves/retrieves settings of an individual calendar * from Android's calendar database. */ -class CalendarDataStore(activity: FragmentActivity, calendarId: Long) : PreferenceDataStore() { +class CalendarDataStore(activity: FragmentActivity, calendarId: Long, isTask: Boolean) : PreferenceDataStore() { private var contentResolver = activity.contentResolver private var calendarUri = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calendarId) + private var taskListrUri = ContentUris.withAppendedId(DmfsOpenTasksContract.TaskLists.PROVIDER_URI, calendarId) + private var isTask = isTask companion object { private val PROJECTION = arrayOf( @@ -39,6 +43,14 @@ class CalendarDataStore(activity: FragmentActivity, calendarId: Long) : Preferen CalendarContract.Calendars.CALENDAR_COLOR, CalendarContract.Calendars.CALENDAR_DISPLAY_NAME ) + + private val TASKLIST_PROJECTION = arrayOf( + DmfsOpenTasksContract.TaskLists.COLUMN_ID, + DmfsOpenTasksContract.TaskLists.COLUMN_SYNC_ENABLE, + DmfsOpenTasksContract.TaskLists.COLUMN_VISIBLE, + DmfsOpenTasksContract.TaskLists.COLUMN_COLOR, + DmfsOpenTasksContract.TaskLists.COLUMN_NAME + ) } private fun mapPreferenceKeyToDatabaseKey(key: String?): String { @@ -51,18 +63,38 @@ class CalendarDataStore(activity: FragmentActivity, calendarId: Long) : Preferen } } + private fun mapPreferenceKeyToDatabaseKeyForTaskList(key: String?): String { + return when (key) { + CalendarPreferences.SYNCHRONIZE_KEY -> DmfsOpenTasksContract.TaskLists.COLUMN_SYNC_ENABLE + CalendarPreferences.VISIBLE_KEY -> DmfsOpenTasksContract.TaskLists.COLUMN_VISIBLE + CalendarPreferences.COLOR_KEY -> DmfsOpenTasksContract.TaskLists.COLUMN_COLOR + CalendarPreferences.DISPLAY_NAME_KEY -> DmfsOpenTasksContract.TaskLists.COLUMN_NAME + else -> throw UnsupportedOperationException("unsupported preference key") + } + } + override fun putBoolean(key: String?, value: Boolean) { - val databaseKey = mapPreferenceKeyToDatabaseKey(key) + val databaseKey: String + val uri: Uri; + if (!isTask) { + databaseKey = mapPreferenceKeyToDatabaseKey(key) + uri = calendarUri + } else { + databaseKey = mapPreferenceKeyToDatabaseKeyForTaskList(key) + uri = taskListrUri + } val values = ContentValues() values.put(databaseKey, if (value) 1 else 0) - contentResolver.update(calendarUri, values, null, null) + contentResolver.update(uri, values, null, null) } override fun getBoolean(key: String?, defValue: Boolean): Boolean { - val databaseKey = mapPreferenceKeyToDatabaseKey(key) + val databaseKey: String = getValues(key)["databaseKey"] as String + val uri: Uri = getValues(key)["uri"] as Uri + val projection: Array = getValues(key)["projection"] as Array - contentResolver.query(calendarUri, PROJECTION, null, null, null)?.use { + contentResolver.query(uri, projection, null, null, null)?.use { if (it.moveToFirst()) { return it.getInt(it.getColumnIndex(databaseKey)) == 1 } @@ -71,17 +103,20 @@ class CalendarDataStore(activity: FragmentActivity, calendarId: Long) : Preferen } override fun putInt(key: String?, value: Int) { - val databaseKey = mapPreferenceKeyToDatabaseKey(key) + val databaseKey: String = getValues(key)["databaseKey"] as String + val uri: Uri = getValues(key)["uri"] as Uri val values = ContentValues() values.put(databaseKey, value) - contentResolver.update(calendarUri, values, null, null) + contentResolver.update(uri, values, null, null) } override fun getInt(key: String?, defValue: Int): Int { - val databaseKey = mapPreferenceKeyToDatabaseKey(key) + val databaseKey: String = getValues(key)["databaseKey"] as String + val uri: Uri = getValues(key)["uri"] as Uri + val projection: Array = getValues(key)["projection"] as Array - contentResolver.query(calendarUri, PROJECTION, null, null, null)?.use { + contentResolver.query(uri, projection, null, null, null)?.use { if (it.moveToFirst()) { return it.getInt(it.getColumnIndex(databaseKey)) } @@ -90,17 +125,20 @@ class CalendarDataStore(activity: FragmentActivity, calendarId: Long) : Preferen } override fun putString(key: String?, value: String?) { - val databaseKey = mapPreferenceKeyToDatabaseKey(key) + val databaseKey: String = getValues(key)["databaseKey"] as String + val uri: Uri = getValues(key)["uri"] as Uri val values = ContentValues() values.put(databaseKey, value) - contentResolver.update(calendarUri, values, null, null) + contentResolver.update(uri, values, null, null) } override fun getString(key: String?, defValue: String?): String? { - val databaseKey = mapPreferenceKeyToDatabaseKey(key) + val databaseKey: String = getValues(key)["databaseKey"] as String + val uri: Uri = getValues(key)["uri"] as Uri + val projection: Array = getValues(key)["projection"] as Array - contentResolver.query(calendarUri, PROJECTION, null, null, null)?.use { + contentResolver.query(uri, projection, null, null, null)?.use { if (it.moveToFirst()) { return it.getString(it.getColumnIndex(databaseKey)) } @@ -108,4 +146,18 @@ class CalendarDataStore(activity: FragmentActivity, calendarId: Long) : Preferen return defValue } + private fun getValues(key: String?) : Map { + var returnMap = HashMap(); + if (!isTask) { + returnMap.put("databaseKey",mapPreferenceKeyToDatabaseKey(key)) + returnMap.put("uri", calendarUri) + returnMap.put("projection", PROJECTION) + } else { + returnMap.put("databaseKey",mapPreferenceKeyToDatabaseKeyForTaskList(key)) + returnMap.put("uri", taskListrUri) + returnMap.put("projection", TASKLIST_PROJECTION) + } + return returnMap + } + } diff --git a/app/src/main/java/com/android/calendar/settings/CalendarPreferences.kt b/app/src/main/java/com/android/calendar/settings/CalendarPreferences.kt index 4743d602d1..29077ee574 100644 --- a/app/src/main/java/com/android/calendar/settings/CalendarPreferences.kt +++ b/app/src/main/java/com/android/calendar/settings/CalendarPreferences.kt @@ -25,7 +25,6 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.os.Bundle import android.provider.CalendarContract -import android.provider.Settings import android.util.TypedValue import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat @@ -35,27 +34,29 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference import com.android.calendar.Utils -import com.android.calendar.alerts.channelId import com.android.calendar.persistence.CalendarRepository +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract import ws.xsoh.etar.R class CalendarPreferences : PreferenceFragmentCompat() { private var calendarId: Long = -1 + private var isTask: Boolean = false; private lateinit var calendarRepository: CalendarRepository private lateinit var account: Account private var numberOfEvents: Long = -1 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { calendarId = requireArguments().getLong(ARG_CALENDAR_ID) + isTask = requireArguments().getBoolean(ARG_IS_TASKS) calendarRepository = CalendarRepository(requireActivity().application) - account = calendarRepository.queryAccount(calendarId)!! - numberOfEvents = calendarRepository.queryNumberOfEvents(calendarId)!! + account = calendarRepository.queryAccount(calendarId, isTask)!! + numberOfEvents = calendarRepository.queryNumberOfEvents(calendarId, isTask)!! // use custom data store to save/retrieve calendar preferences in Android's calendar database val preferenceManager = preferenceManager - preferenceManager.preferenceDataStore = CalendarDataStore(requireActivity(), calendarId) + preferenceManager.preferenceDataStore = CalendarDataStore(requireActivity(), calendarId, isTask) populatePreferences() } @@ -71,6 +72,7 @@ class CalendarPreferences : PreferenceFragmentCompat() { val screen = preferenceManager.createPreferenceScreen(context) val isLocalAccount = account.type == CalendarContract.ACCOUNT_TYPE_LOCAL + || account.type == DmfsOpenTasksContract.ACCOUNT_TYPE_LOCAL val currentColor = preferenceManager.preferenceDataStore!!.getInt(COLOR_KEY, -1) val authenticatorInfo = getAuthenticatorInfo(account) @@ -87,25 +89,35 @@ class CalendarPreferences : PreferenceFragmentCompat() { title = getString(R.string.preferences_calendar_color) icon = getColorIcon(currentColor) } - colorPreference.setOnPreferenceClickListener { - displayCalendarColorPicker() - true + // disable it cause we need a sync adapter to update tasks db + if (!isTask) { + colorPreference.setOnPreferenceClickListener { + displayCalendarColorPicker() + true + } } val displayNamePreference = EditTextPreference(context).apply { key = DISPLAY_NAME_KEY title = getString(R.string.preferences_calendar_display_name) dialogTitle = getString(R.string.preferences_calendar_display_name) } - displayNamePreference.setOnPreferenceChangeListener { _, newValue -> - activity?.title = newValue as String - true + // disable it cause we need a sync adapter to update tasks db + if (!isTask) { + displayNamePreference.setOnPreferenceChangeListener { _, newValue -> + activity?.title = newValue as String + true + } } + val deletePreference = Preference(context).apply { title = getString(R.string.preferences_calendar_delete) } - deletePreference.setOnPreferenceClickListener { - deleteCalendar() - true + // disable it cause we need a sync adapter to update tasks db + if (!isTask) { + deletePreference.setOnPreferenceClickListener { + deleteCalendar() + true + } } val configurePreference = Preference(context).apply { title = getString(R.string.preferences_calendar_configure_account, authenticatorInfo?.label) @@ -135,25 +147,13 @@ class CalendarPreferences : PreferenceFragmentCompat() { isSelectable = false } + if (!isLocalAccount) { screen.addPreference(synchronizePreference) } - screen.addPreference(visiblePreference) - - if(Utils.isOreoOrLater()){ - val notificationPreference = Preference(context).apply { - title = getString(R.string.preferences_manage_notifications) - intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { - putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) - putExtra(Settings.EXTRA_CHANNEL_ID, channelId(this@CalendarPreferences.calendarId)) - } - } - screen.addPreference(notificationPreference) - } - screen.addPreference(colorPreference) - if (isLocalAccount) { + if (isLocalAccount && !isTask) { screen.addPreference(displayNamePreference) screen.addPreference(deletePreference) } @@ -258,7 +258,7 @@ class CalendarPreferences : PreferenceFragmentCompat() { const val COLOR_PICKER_DIALOG_TAG = "CalendarColorPickerDialog" const val ARG_CALENDAR_ID = "calendarId" - + const val ARG_IS_TASKS = "isTasks" const val SYNCHRONIZE_KEY = "synchronize" const val VISIBLE_KEY = "visible" const val COLOR_KEY = "color" diff --git a/app/src/main/java/com/android/calendar/settings/GeneralPreferences.kt b/app/src/main/java/com/android/calendar/settings/GeneralPreferences.kt index 1597105392..c5d9b03d9d 100644 --- a/app/src/main/java/com/android/calendar/settings/GeneralPreferences.kt +++ b/app/src/main/java/com/android/calendar/settings/GeneralPreferences.kt @@ -64,7 +64,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), private lateinit var colorPref: Preference private lateinit var realEventColors: SwitchPreference private lateinit var pureBlackNightModePref: SwitchPreference - private lateinit var doNotCheckBatteryOptimizationPref: SwitchPreference private lateinit var defaultStartPref: ListPreference private lateinit var hideDeclinedPref: SwitchPreference private lateinit var weekStartPref: ListPreference @@ -74,7 +73,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), private lateinit var homeTzPref: Preference private lateinit var popupPref: SwitchPreference private lateinit var snoozeDelayPref: ListPreference - private lateinit var useDefaultCustomSnoozeDelayPref: Preference private lateinit var defaultReminderPref: ListPreference private lateinit var copyDbPref: Preference private lateinit var skipRemindersPref: ListPreference @@ -110,7 +108,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), colorPref = preferenceScreen.findPreference(KEY_COLOR_PREF)!! realEventColors = preferenceScreen.findPreference(KEY_REAL_EVENT_COLORS)!! pureBlackNightModePref = preferenceScreen.findPreference(KEY_PURE_BLACK_NIGHT_MODE)!! - doNotCheckBatteryOptimizationPref = preferenceScreen.findPreference(KEY_DO_NOT_CHECK_BATTERY_OPTIMIZATION)!! defaultStartPref = preferenceScreen.findPreference(KEY_DEFAULT_START)!! hideDeclinedPref = preferenceScreen.findPreference(KEY_HIDE_DECLINED)!! weekStartPref = preferenceScreen.findPreference(KEY_WEEK_START_DAY)!! @@ -120,7 +117,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), homeTzPref = preferenceScreen.findPreference(KEY_HOME_TZ)!! popupPref = preferenceScreen.findPreference(KEY_ALERTS_POPUP)!! snoozeDelayPref = preferenceScreen.findPreference(KEY_DEFAULT_SNOOZE_DELAY)!! - useDefaultCustomSnoozeDelayPref = preferenceScreen.findPreference(KEY_USE_CUSTOM_SNOOZE_DELAY)!! defaultReminderPref = preferenceScreen.findPreference(KEY_DEFAULT_REMINDER)!! copyDbPref = preferenceScreen.findPreference(KEY_OTHER_COPY_DB)!! skipRemindersPref = preferenceScreen.findPreference(KEY_OTHER_REMINDERS_RESPONDED)!! @@ -158,7 +154,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), buildSnoozeDelayEntries() buildDefaultReminderPrefEntries() - handleUseCustomSnoozeDelayVisibility() defaultEventDurationPref.summary = defaultEventDurationPref.entry themePref.summary = themePref.entry weekStartPref.summary = weekStartPref.entry @@ -191,10 +186,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), initializeColorMap() } - private fun handleUseCustomSnoozeDelayVisibility() { - useDefaultCustomSnoozeDelayPref.isEnabled = Integer.parseInt(snoozeDelayPref.value) >= 0 - } - private fun showColorPickerDialog() { val colorPickerDialog = ColorPickerDialogX() val selectedColorName = Utils.getSharedPreference(activity, KEY_COLOR_PREF, "teal") @@ -240,7 +231,7 @@ class GeneralPreferences : PreferenceFragmentCompat(), override fun onStart() { super.onStart() - preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) setPreferenceListeners(this) } @@ -251,7 +242,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), themePref.onPreferenceChangeListener = listener colorPref.onPreferenceChangeListener = listener pureBlackNightModePref.onPreferenceChangeListener = listener - doNotCheckBatteryOptimizationPref.onPreferenceChangeListener = listener defaultStartPref.onPreferenceChangeListener = listener hideDeclinedPref.onPreferenceChangeListener = listener weekStartPref.onPreferenceChangeListener = listener @@ -268,13 +258,12 @@ class GeneralPreferences : PreferenceFragmentCompat(), } override fun onStop() { - preferenceScreen.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) + preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) super.onStop() } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { val a = activity ?: return - key ?: return BackupManager.dataChanged(a.packageName) @@ -362,7 +351,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), snoozeDelayPref -> { snoozeDelayPref.value = newValue as String snoozeDelayPref.summary = snoozeDelayPref.entry - handleUseCustomSnoozeDelayVisibility() } defaultStartPref -> { val i = defaultStartPref.findIndexOfValue(newValue as String) @@ -415,8 +403,8 @@ class GeneralPreferences : PreferenceFragmentCompat(), defaultReminderPref.entries = entries } - override fun onPreferenceTreeClick(preference: Preference): Boolean { - when (preference.key) { + override fun onPreferenceTreeClick(preference: Preference?): Boolean { + when (preference!!.key) { KEY_COLOR_PREF -> { showColorPickerDialog() return true @@ -524,7 +512,6 @@ class GeneralPreferences : PreferenceFragmentCompat(), const val KEY_THEME_PREF = "pref_theme" const val KEY_COLOR_PREF = "pref_color" const val KEY_REAL_EVENT_COLORS = "pref_real_event_colors" - const val KEY_DO_NOT_CHECK_BATTERY_OPTIMIZATION = "pref_do_not_check_battery_optimization" const val KEY_PURE_BLACK_NIGHT_MODE = "pref_pure_black_night_mode" const val KEY_DEFAULT_START = "preferences_default_start" const val KEY_HIDE_DECLINED = "preferences_hide_declined" @@ -541,8 +528,8 @@ class GeneralPreferences : PreferenceFragmentCompat(), const val KEY_ALERTS_POPUP = "preferences_alerts_popup" const val KEY_SHOW_CONTROLS = "preferences_show_controls" const val KEY_DEFAULT_REMINDER = "preferences_default_reminder" - const val NO_REMINDER = -2147483648 - const val NO_REMINDER_STRING = "-2147483648" + const val NO_REMINDER = -1 + const val NO_REMINDER_STRING = "-1" const val REMINDER_DEFAULT_TIME = 10 // in minutes const val KEY_USE_CUSTOM_SNOOZE_DELAY = "preferences_custom_snooze_delay" const val KEY_DEFAULT_SNOOZE_DELAY = "preferences_default_snooze_delay" diff --git a/app/src/main/java/com/android/calendar/settings/MainListPreferences.kt b/app/src/main/java/com/android/calendar/settings/MainListPreferences.kt index eacc79f3d5..2cc265da2b 100644 --- a/app/src/main/java/com/android/calendar/settings/MainListPreferences.kt +++ b/app/src/main/java/com/android/calendar/settings/MainListPreferences.kt @@ -75,9 +75,9 @@ class MainListPreferences : PreferenceFragmentCompat() { val accountCategoryUniqueKey = "account_category_${calendar.accountName}_${calendar.accountType}" var accountCategory = screen.findPreference(accountCategoryUniqueKey) if (accountCategory == null) { - accountCategory = PreferenceCategory(requireContext()).apply { + accountCategory = PreferenceCategory(context).apply { key = accountCategoryUniqueKey - title = calendar.accountName + title = if (calendar.isTasks) "My task" else calendar.accountName icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_account_circle) order = if (calendar.isLocal) 10 else 11 // show offline calendar first isOrderingAsAdded = false // use alphabetic ordering for children @@ -86,10 +86,10 @@ class MainListPreferences : PreferenceFragmentCompat() { } // add preference per calendar if not already present - val calendarUniqueKey = "calendar_preference_${calendar.id}" + val calendarUniqueKey = "calendar_preference_${calendar.id}_${calendar.name}" var calendarPreference = screen.findPreference(calendarUniqueKey) if (calendarPreference == null) { - calendarPreference = Preference(requireContext()) + calendarPreference = Preference(context) accountCategory.addPreference(calendarPreference) } calendarPreference.apply { @@ -97,11 +97,12 @@ class MainListPreferences : PreferenceFragmentCompat() { title = if (calendar.displayName.isNullOrBlank()) getString(R.string.preferences_calendar_no_display_name) else calendar.displayName fragment = CalendarPreferences::class.java.name order = if (calendar.isPrimary) 1 else 2 // primary calendar is first, others are alphabetically ordered below - icon = getCalendarIcon(calendar.color, calendar.visible, calendar.syncEvents) + icon = getCalendarIcon(calendar.color, calendar.visible, calendar.syncEvents, calendar.isTasks) summary = getCalendarSummary(calendar.visible, calendar.syncEvents) } // pass-through calendar id for CalendarPreferences calendarPreference.extras.putLong(CalendarPreferences.ARG_CALENDAR_ID, calendar.id) + calendarPreference.extras.putBoolean(CalendarPreferences.ARG_IS_TASKS, calendar.isTasks) } // remove preferences for calendars no longer existing @@ -138,11 +139,17 @@ class MainListPreferences : PreferenceFragmentCompat() { } } - private fun getCalendarIcon(color: Int, visible: Boolean, syncEvents: Boolean): Drawable { + private fun getCalendarIcon(color: Int, visible: Boolean, syncEvents: Boolean, isTask: Boolean): Drawable { val icon = if (!syncEvents) { ContextCompat.getDrawable(requireContext(), R.drawable.ic_sync_off_light) - } else if (visible) { + } else if (visible && !isTask) { ContextCompat.getDrawable(requireContext(), R.drawable.circle) + } else if (visible && isTask) { + ContextCompat.getDrawable(requireContext(), R.drawable.square) + } else if (!visible && !isTask) { + ContextCompat.getDrawable(requireContext(), R.drawable.circle_outline) + } else if (!visible && isTask) { + ContextCompat.getDrawable(requireContext(), R.drawable.square_outline) } else { ContextCompat.getDrawable(requireContext(), R.drawable.circle_outline) } @@ -152,12 +159,12 @@ class MainListPreferences : PreferenceFragmentCompat() { } private fun addGeneralPreferences(screen: PreferenceScreen) { - val generalPreference = Preference(requireContext()).apply { + val generalPreference = Preference(context).apply { title = getString(R.string.preferences_list_general) icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_settings) fragment = GeneralPreferences::class.java.name } - val addCaldavPreference = Preference(requireContext()).apply { + val addCaldavPreference = Preference(context).apply { title = getString(R.string.preferences_list_add_remote) icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_add) } @@ -165,7 +172,7 @@ class MainListPreferences : PreferenceFragmentCompat() { launchDavX5Login() true } - val addEtesyncPreference = Preference(requireContext()).apply { + val addEtesyncPreference = Preference(context).apply { title = getString(R.string.preferences_list_add_remote_etesync) icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_add) } @@ -173,7 +180,7 @@ class MainListPreferences : PreferenceFragmentCompat() { launchAddEtesync() true } - val addOfflinePreference = Preference(requireContext()).apply { + val addOfflinePreference = Preference(context).apply { title = getString(R.string.preferences_list_add_offline) icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_add) } diff --git a/app/src/main/java/com/android/calendar/settings/QuickResponsePreferences.kt b/app/src/main/java/com/android/calendar/settings/QuickResponsePreferences.kt index 6e167d788a..312f85c957 100644 --- a/app/src/main/java/com/android/calendar/settings/QuickResponsePreferences.kt +++ b/app/src/main/java/com/android/calendar/settings/QuickResponsePreferences.kt @@ -53,7 +53,7 @@ class QuickResponsePreferences : PreferenceFragmentCompat(), Preference.OnPrefer responsePreferences = arrayOfNulls(responses.size) for ((i, response) in responses.withIndex()) { - val responsePreference = EditTextPreference(requireActivity()).apply { + val responsePreference = EditTextPreference(activity).apply { setDialogTitle(R.string.quick_response_settings_edit_title) title = response text = response diff --git a/app/src/main/java/com/android/calendar/settings/SettingsActivity.kt b/app/src/main/java/com/android/calendar/settings/SettingsActivity.kt index 8f43082098..6ec224f14e 100644 --- a/app/src/main/java/com/android/calendar/settings/SettingsActivity.kt +++ b/app/src/main/java/com/android/calendar/settings/SettingsActivity.kt @@ -81,7 +81,7 @@ class SettingsActivity : AppCompatActivity(), val args = pref.extras val fragment = supportFragmentManager.fragmentFactory.instantiate( classLoader, - pref.fragment!! + pref.fragment ).apply { arguments = args setTargetFragment(caller, 0) diff --git a/app/src/main/java/com/android/calendar/settings/ViewDetailsPreferences.kt b/app/src/main/java/com/android/calendar/settings/ViewDetailsPreferences.kt index 420876785e..aa2868235e 100644 --- a/app/src/main/java/com/android/calendar/settings/ViewDetailsPreferences.kt +++ b/app/src/main/java/com/android/calendar/settings/ViewDetailsPreferences.kt @@ -77,7 +77,7 @@ class ViewDetailsPreferences : PreferenceFragmentCompat() { val newEntries = Arrays.copyOf(entries, entries.size - 1) displayTime.entries = newEntries } - if (displayTime.entry == null || displayTime.entry!!.isEmpty()) { + if (displayTime.entry == null || displayTime.entry.isEmpty()) { displayTime.value = getDefaultTimeToShow(activity).toString() } } @@ -169,11 +169,11 @@ class ViewDetailsPreferences : PreferenceFragmentCompat() { return if (Utils.getConfigBool(context, R.bool.show_time_in_month)) TimeVisibility.SHOW_TIME_RANGE_BELOW.value else TimeVisibility.SHOW_NONE.value } - fun getPreferences(context: Context?): Preferences { + fun getPreferences(context: Context?): Preferences? { return Preferences(context!!) } - fun setDefaultValues(context: Context) { + fun setDefaultValues(context: Context?) { PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE, R.xml.view_details_preferences, true) } diff --git a/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetModel.java b/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetModel.java index d454d6ba33..f3ea893762 100644 --- a/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetModel.java +++ b/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetModel.java @@ -28,6 +28,10 @@ import com.android.calendarcommon2.Time; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.TimeZone; @@ -50,7 +54,7 @@ class CalendarAppWidgetModel { public CalendarAppWidgetModel(Context context, String timeZone) { mNow = System.currentTimeMillis(); Time time = new Time(timeZone); - time.set(mNow); // This is needed for gmtoff to be set + time.set(System.currentTimeMillis()); // This is needed for gmtoff to be set mTodayJulianDay = Time.getJulianDay(mNow, time.getGmtOffset()); mMaxJulianDay = mTodayJulianDay + CalendarAppWidgetService.MAX_DAYS - 1; mEventInfos = new ArrayList(50); @@ -59,12 +63,14 @@ public CalendarAppWidgetModel(Context context, String timeZone) { mContext = context; } - public void buildFromCursor(Cursor cursor, String timeZone) { + public void buildFromCursor(Cursor cursor, String timeZone, Boolean isTask) { + final Time recycle = new Time(timeZone); final ArrayList> mBuckets = new ArrayList>(CalendarAppWidgetService.MAX_DAYS); for (int i = 0; i < CalendarAppWidgetService.MAX_DAYS; i++) { mBuckets.add(new LinkedList()); } + recycle.set(System.currentTimeMillis()); mShowTZ = !TextUtils.equals(timeZone, Utils.getCurrentTimezone()); if (mShowTZ) { mHomeTZName = TimeZone.getTimeZone(timeZone).getDisplayName(false, TimeZone.SHORT); @@ -78,20 +84,32 @@ public void buildFromCursor(Cursor cursor, String timeZone) { final boolean allDay = cursor.getInt(CalendarAppWidgetService.INDEX_ALL_DAY) != 0; long start = cursor.getLong(CalendarAppWidgetService.INDEX_BEGIN); long end = cursor.getLong(CalendarAppWidgetService.INDEX_END); + if (isTask) { + Calendar instance = Calendar.getInstance(); + instance.setTimeInMillis(end); + instance.add(Calendar.MINUTE, -30); + start = instance.getTimeInMillis(); + } final String title = cursor.getString(CalendarAppWidgetService.INDEX_TITLE); final String location = cursor.getString(CalendarAppWidgetService.INDEX_EVENT_LOCATION); // we don't compute these ourselves because it seems to produce the // wrong endDay for all day events - final int startDay = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY); - final int endDay = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY); + final int startDay; + final int endDay; + if (isTask) { + startDay = Time.getJulianDay(start, new Time().getGmtOffset()); + endDay = Time.getJulianDay(end, new Time().getGmtOffset()); + } else { + startDay = cursor.getInt(CalendarAppWidgetService.INDEX_START_DAY); + endDay = cursor.getInt(CalendarAppWidgetService.INDEX_END_DAY); + } final int color = cursor.getInt(CalendarAppWidgetService.INDEX_COLOR); final int selfStatus = cursor .getInt(CalendarAppWidgetService.INDEX_SELF_ATTENDEE_STATUS); // Adjust all-day times into local timezone if (allDay) { - final Time recycle = new Time(); start = Utils.convertAlldayUtcToLocal(recycle, start, tz); end = Utils.convertAlldayUtcToLocal(recycle, end, tz); } @@ -110,18 +128,34 @@ public void buildFromCursor(Cursor cursor, String timeZone) { int i = mEventInfos.size(); mEventInfos.add(populateEventInfo(eventId, allDay, start, end, startDay, endDay, title, location, color, selfStatus)); + } + } + + public void populateBuckets(String timeZone) { + final Time recycle = new Time(timeZone); + recycle.set(System.currentTimeMillis()); + Collections.sort(mEventInfos, Comparator.comparing(u -> new Date(u.start))); + + final ArrayList> mBuckets = + new ArrayList>(CalendarAppWidgetService.MAX_DAYS); + for (int i = 0; i < CalendarAppWidgetService.MAX_DAYS; i++) { + mBuckets.add(new LinkedList()); + } + int i = 0; + for (EventInfo mEventInfo : mEventInfos) { // populate the day buckets that this event falls into - int from = Math.max(startDay, mTodayJulianDay); - int to = Math.min(endDay, mMaxJulianDay); + int from = Math.max(Time.getJulianDay(mEventInfo.start, new Time().getGmtOffset()), mTodayJulianDay); + int to = Math.min(Time.getJulianDay(mEventInfo.end, new Time().getGmtOffset()), mMaxJulianDay); for (int day = from; day <= to; day++) { LinkedList bucket = mBuckets.get(day - mTodayJulianDay); RowInfo rowInfo = new RowInfo(RowInfo.TYPE_MEETING, i); - if (allDay) { + if (mEventInfo.allDay) { bucket.addFirst(rowInfo); } else { bucket.add(rowInfo); } } + i++; } int day = mTodayJulianDay; @@ -130,11 +164,12 @@ public void buildFromCursor(Cursor cursor, String timeZone) { if (!bucket.isEmpty()) { // We don't show day header in today if (day != mTodayJulianDay) { - final Time recycle = new Time(timeZone); final DayInfo dayInfo = populateDayInfo(day, recycle); // Add the day header final int dayIndex = mDayInfos.size(); - mDayInfos.add(dayInfo); + if (!mDayInfos.contains(dayInfo)) { + mDayInfos.add(dayInfo); + } mRowInfos.add(new RowInfo(RowInfo.TYPE_DAY, dayIndex)); } @@ -204,16 +239,15 @@ private EventInfo populateEventInfo(long eventId, boolean allDay, long start, lo private DayInfo populateDayInfo(int julianDay, Time recycle) { long millis = recycle.setJulianDay(julianDay); - int flags = - DateUtils.FORMAT_ABBREV_ALL | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_WEEKDAY; + int flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE; String label; if (julianDay == mTodayJulianDay + 1) { + flags |= DateUtils.FORMAT_SHOW_WEEKDAY; label = mContext.getString(R.string.agenda_tomorrow, Utils.formatDateRange(mContext, millis, millis, flags)); } else { + flags |= DateUtils.FORMAT_SHOW_WEEKDAY; label = Utils.formatDateRange(mContext, millis, millis, flags); } return new DayInfo(julianDay, label); diff --git a/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetProvider.java b/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetProvider.java index 427cadabeb..bfcbd594fc 100644 --- a/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetProvider.java +++ b/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetProvider.java @@ -42,6 +42,8 @@ import com.android.calendar.event.EditEventActivity; import com.android.calendarcommon2.Time; +import java.util.Calendar; + import ws.xsoh.etar.R; /** @@ -139,7 +141,7 @@ public void onReceive(Context context, Intent intent) { // coming in without extras, which AppWidgetProvider then blocks. final String action = intent.getAction(); if (LOGD) - Log.d(TAG, "AppWidgetProvider got the intent: " + intent); + Log.d(TAG, "AppWidgetProvider got the intent: " + intent.toString()); if (Utils.getWidgetUpdateAction(context).equals(action)) { if (!isWidgetSupported(context)) { return; diff --git a/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetService.java b/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetService.java index d26d10f414..50b2b771da 100644 --- a/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetService.java +++ b/app/src/main/java/com/android/calendar/widget/CalendarAppWidgetService.java @@ -24,6 +24,7 @@ import android.content.CursorLoader; import android.content.Intent; import android.content.Loader; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; import android.database.MatrixCursor; @@ -40,8 +41,11 @@ import android.widget.RemoteViews; import android.widget.RemoteViewsService; +import androidx.core.content.ContextCompat; import com.android.calendar.DynamicTheme; +import com.android.calendar.Event; import com.android.calendar.Utils; +import com.android.calendar.persistence.tasks.DmfsOpenTasksContract; import com.android.calendar.widget.CalendarAppWidgetModel.DayInfo; import com.android.calendar.widget.CalendarAppWidgetModel.EventInfo; import com.android.calendar.widget.CalendarAppWidgetModel.RowInfo; @@ -70,6 +74,19 @@ public class CalendarAppWidgetService extends RemoteViewsService { Instances.DISPLAY_COLOR, Instances.SELF_ATTENDEE_STATUS, }; + + static final String[] TASK_PROJECTION = new String[]{ + DmfsOpenTasksContract.Tasks.COLUMN_IS_ALLDAY, + DmfsOpenTasksContract.Tasks.COLUMN_START_DATE, + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE, + DmfsOpenTasksContract.Tasks.COLUMN_TITLE, + DmfsOpenTasksContract.Tasks.COLUMN_LOCATION, + DmfsOpenTasksContract.Tasks.COLUMN_ID, + DmfsOpenTasksContract.Tasks.COLUMN_START_DATE, + DmfsOpenTasksContract.Tasks.COLUMN_DUE_DATE, + DmfsOpenTasksContract.Tasks.COLUMN_LIST_COLOR, + "1 as selfAttendeeStatus", + }; static final int INDEX_ALL_DAY = 0; static final int INDEX_BEGIN = 1; static final int INDEX_END = 2; @@ -173,7 +190,7 @@ public CalendarFactory() { protected static CalendarAppWidgetModel buildAppWidgetModel( Context context, Cursor cursor, String timeZone) { CalendarAppWidgetModel model = new CalendarAppWidgetModel(context, timeZone); - model.buildFromCursor(cursor, timeZone); + model.buildFromCursor(cursor, timeZone, false); return model; } @@ -511,6 +528,20 @@ public void onLoadComplete(Loader loader, Cursor cursor) { MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor); try { mModel = buildAppWidgetModel(mContext, matrixCursor, tz); + long begin = now - DateUtils.DAY_IN_MILLIS; + long end = now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS; + + int mFirstLoadedJulianDay = Time.getJulianDay(begin, new Time().getGmtOffset()); + int mLastLoadedJulianDay = Time.getJulianDay(end, new Time().getGmtOffset()); + + if (ContextCompat.checkSelfPermission(mContext, DmfsOpenTasksContract.TASK_READ_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + Cursor cTasks = Event.instancesQueryForTasks(mContext.getContentResolver(), TASK_PROJECTION, mFirstLoadedJulianDay, mLastLoadedJulianDay); + MatrixCursor matrixCursorTasks = Utils.matrixCursorFromCursor(cTasks); + mModel.buildFromCursor(matrixCursorTasks, tz, true); + } + + mModel.populateBuckets(tz); } finally { if (matrixCursor != null) { matrixCursor.close(); diff --git a/app/src/main/res/drawable-hdpi/stat_notify_calendar.webp b/app/src/main/res/drawable-hdpi/stat_notify_calendar.webp new file mode 100644 index 0000000000..9e40ee5950 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/stat_notify_calendar.webp differ diff --git a/app/src/main/res/drawable-mdpi/stat_notify_calendar.webp b/app/src/main/res/drawable-mdpi/stat_notify_calendar.webp new file mode 100644 index 0000000000..30e7634cce Binary files /dev/null and b/app/src/main/res/drawable-mdpi/stat_notify_calendar.webp differ diff --git a/app/src/main/res/drawable-xhdpi/stat_notify_calendar.webp b/app/src/main/res/drawable-xhdpi/stat_notify_calendar.webp new file mode 100644 index 0000000000..27a6e85d05 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/stat_notify_calendar.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/stat_notify_calendar.webp b/app/src/main/res/drawable-xxhdpi/stat_notify_calendar.webp new file mode 100644 index 0000000000..cfe7869efd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/stat_notify_calendar.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/stat_notify_calendar.webp b/app/src/main/res/drawable-xxxhdpi/stat_notify_calendar.webp new file mode 100644 index 0000000000..bec7a2487b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/stat_notify_calendar.webp differ diff --git a/app/src/main/res/drawable/ic_colorpicker.xml b/app/src/main/res/drawable/ic_colorpicker.xml index af5a4d14ec..4abeea58f9 100644 --- a/app/src/main/res/drawable/ic_colorpicker.xml +++ b/app/src/main/res/drawable/ic_colorpicker.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_error_outline.xml b/app/src/main/res/drawable/ic_error_outline.xml deleted file mode 100644 index 7b4fbf9a26..0000000000 --- a/app/src/main/res/drawable/ic_error_outline.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/square.xml b/app/src/main/res/drawable/square.xml new file mode 100644 index 0000000000..70bc1b65bf --- /dev/null +++ b/app/src/main/res/drawable/square.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/square_outline.xml b/app/src/main/res/drawable/square_outline.xml new file mode 100644 index 0000000000..c6bba3d544 --- /dev/null +++ b/app/src/main/res/drawable/square_outline.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/stat_notify_calendar_events.xml b/app/src/main/res/drawable/stat_notify_calendar_events.xml deleted file mode 100644 index 64be285eb3..0000000000 --- a/app/src/main/res/drawable/stat_notify_calendar_events.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/stat_notify_refresh_events.xml b/app/src/main/res/drawable/stat_notify_refresh_events.xml deleted file mode 100644 index f2fede3523..0000000000 --- a/app/src/main/res/drawable/stat_notify_refresh_events.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/edit_event_all.xml b/app/src/main/res/layout/edit_event_all.xml index 37fd4252bb..e4858ae2c4 100644 --- a/app/src/main/res/layout/edit_event_all.xml +++ b/app/src/main/res/layout/edit_event_all.xml @@ -59,7 +59,7 @@ android:inputType="textAutoCorrect|textCapSentences" android:minHeight="48dip" android:singleLine="true" - android:textSize="20sp" + android:textSize="24sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -137,17 +137,19 @@ android:id="@+id/change_color_new_event" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="24dp" - android:layout_marginTop="20dp" + android:layout_marginEnd="32dp" android:background="?android:attr/selectableItemBackground" android:contentDescription="@string/choose_event_color_label" android:enabled="false" android:scaleType="center" + android:scaleX="2" + android:scaleY="2" android:src="@drawable/ic_colorpicker" + android:tint="?attr/colorButtonNormal" android:visibility="invisible" app:layout_constraintBottom_toBottomOf="@+id/calendar_selector_group_icon" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="@+id/view" /> + app:layout_constraintTop_toTopOf="@+id/calendar_selector_group_icon" /> - - +