Merge "AOSP/DeskClock - Add Kotlin for Weekdays and Widget data files"
diff --git a/Android.bp b/Android.bp
index 35ea8ad..ddb4de6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -73,6 +73,9 @@
"src/**/deskclock/data/TimerNotificationBuilder.java",
"src/**/deskclock/data/TimerStringFormatter.java",
"src/**/deskclock/data/TimeZones.java",
+ "src/**/deskclock/data/Weekdays.java",
+ "src/**/deskclock/data/WidgetDAO.java",
+ "src/**/deskclock/data/WidgetModel.java",
"src/**/deskclock/events/*.java",
"src/**/deskclock/provider/*.java",
"src/**/deskclock/settings/*.java",
diff --git a/src/com/android/deskclock/data/Weekdays.kt b/src/com/android/deskclock/data/Weekdays.kt
new file mode 100644
index 0000000..3f792fc
--- /dev/null
+++ b/src/com/android/deskclock/data/Weekdays.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2020 The Android Open Source 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.deskclock.data
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+
+import com.android.deskclock.R
+
+import java.text.DateFormatSymbols
+import java.util.Calendar
+
+/**
+ * This class is responsible for encoding a weekly repeat cycle in a [bitset][.getBits]. It
+ * also converts between those bits and the [Calendar.DAY_OF_WEEK] values for easier mutation
+ * and querying.
+ */
+class Weekdays private constructor(bits: Int) {
+ /**
+ * The preferred starting day of the week can differ by locale. This enumerated value is used to
+ * describe the preferred ordering.
+ */
+ enum class Order(vararg calendarDays: Int) {
+ SAT_TO_FRI(Calendar.SATURDAY, Calendar.SUNDAY, Calendar.MONDAY,
+ Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY),
+ SUN_TO_SAT(Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY,
+ Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY),
+ MON_TO_SUN(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY,
+ Calendar.FRIDAY, Calendar.SATURDAY, Calendar.SUNDAY);
+
+ val calendarDays: List<Int> = calendarDays.asList()
+ }
+
+ companion object {
+ /** All valid bits set. */
+ private const val ALL_DAYS = 0x7F
+
+ /** An instance with all weekdays in the weekly repeat cycle. */
+ @JvmField
+ val ALL = fromBits(ALL_DAYS)
+
+ /** An instance with no weekdays in the weekly repeat cycle. */
+ @JvmField
+ val NONE = fromBits(0)
+
+ /** Maps calendar weekdays to the bit masks that represent them in this class. */
+ private val sCalendarDayToBit: Map<Int, Int>
+
+ init {
+ val map: MutableMap<Int, Int> = mutableMapOf()
+ map[Calendar.MONDAY] = 0x01
+ map[Calendar.TUESDAY] = 0x02
+ map[Calendar.WEDNESDAY] = 0x04
+ map[Calendar.THURSDAY] = 0x08
+ map[Calendar.FRIDAY] = 0x10
+ map[Calendar.SATURDAY] = 0x20
+ map[Calendar.SUNDAY] = 0x40
+ sCalendarDayToBit = map
+ }
+
+ /**
+ * @param bits [bits][.getBits] representing the encoded weekly repeat schedule
+ * @return a Weekdays instance representing the same repeat schedule as the `bits`
+ */
+ @JvmStatic
+ fun fromBits(bits: Int): Weekdays {
+ return Weekdays(bits)
+ }
+
+ /**
+ * @param calendarDays an array containing any or all of the following values
+ *
+ * * [Calendar.SUNDAY]
+ * * [Calendar.MONDAY]
+ * * [Calendar.TUESDAY]
+ * * [Calendar.WEDNESDAY]
+ * * [Calendar.THURSDAY]
+ * * [Calendar.FRIDAY]
+ * * [Calendar.SATURDAY]
+ *
+ * @return a Weekdays instance representing the given `calendarDays`
+ */
+ @JvmStatic
+ fun fromCalendarDays(vararg calendarDays: Int): Weekdays {
+ var bits = 0
+ for (calendarDay in calendarDays) {
+ val bit = sCalendarDayToBit[calendarDay]
+ if (bit != null) {
+ bits = bits or bit
+ }
+ }
+ return Weekdays(bits)
+ }
+ }
+
+ /** An encoded form of a weekly repeat schedule. */
+ val bits: Int = ALL_DAYS and bits
+
+ /**
+ * @param calendarDay any of the following values
+ *
+ * * [Calendar.SUNDAY]
+ * * [Calendar.MONDAY]
+ * * [Calendar.TUESDAY]
+ * * [Calendar.WEDNESDAY]
+ * * [Calendar.THURSDAY]
+ * * [Calendar.FRIDAY]
+ * * [Calendar.SATURDAY]
+ *
+ * @param on `true` if the `calendarDay` is on; `false` otherwise
+ * @return a WeekDays instance with the `calendarDay` mutated
+ */
+ fun setBit(calendarDay: Int, on: Boolean): Weekdays {
+ val bit = sCalendarDayToBit[calendarDay] ?: return this
+ return Weekdays(if (on) bits or bit else bits and bit.inv())
+ }
+
+ /**
+ * @param calendarDay any of the following values
+ *
+ * * [Calendar.SUNDAY]
+ * * [Calendar.MONDAY]
+ * * [Calendar.TUESDAY]
+ * * [Calendar.WEDNESDAY]
+ * * [Calendar.THURSDAY]
+ * * [Calendar.FRIDAY]
+ * * [Calendar.SATURDAY]
+ *
+ * @return `true` if the given `calendarDay`
+ */
+ fun isBitOn(calendarDay: Int): Boolean {
+ val bit = sCalendarDayToBit[calendarDay]
+ ?: throw IllegalArgumentException("$calendarDay is not a valid weekday")
+ return bits and bit > 0
+ }
+
+ /**
+ * @return `true` iff at least one weekday is enabled in the repeat schedule
+ */
+ val isRepeating: Boolean
+ get() = bits != 0
+
+ /**
+ * Note: only the day-of-week is read from the `time`. The time fields
+ * are not considered in this computation.
+ *
+ * @param time a timestamp relative to which the answer is given
+ * @return the number of days between the given `time` and the previous enabled weekday
+ * which is always between 1 and 7 inclusive; `-1` if no weekdays are enabled
+ */
+ fun getDistanceToPreviousDay(time: Calendar): Int {
+ var calendarDay = time[Calendar.DAY_OF_WEEK]
+ for (count in 1..7) {
+ calendarDay--
+ if (calendarDay < Calendar.SUNDAY) {
+ calendarDay = Calendar.SATURDAY
+ }
+ if (isBitOn(calendarDay)) {
+ return count
+ }
+ }
+
+ return -1
+ }
+
+ /**
+ * Note: only the day-of-week is read from the `time`. The time fields
+ * are not considered in this computation.
+ *
+ * @param time a timestamp relative to which the answer is given
+ * @return the number of days between the given `time` and the next enabled weekday which
+ * is always between 0 and 6 inclusive; `-1` if no weekdays are enabled
+ */
+ fun getDistanceToNextDay(time: Calendar): Int {
+ var calendarDay = time[Calendar.DAY_OF_WEEK]
+ for (count in 0..6) {
+ if (isBitOn(calendarDay)) {
+ return count
+ }
+
+ calendarDay++
+ if (calendarDay > Calendar.SATURDAY) {
+ calendarDay = Calendar.SUNDAY
+ }
+ }
+
+ return -1
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || javaClass != other.javaClass) return false
+
+ val weekdays = other as Weekdays
+ return bits == weekdays.bits
+ }
+
+ override fun hashCode(): Int {
+ return bits
+ }
+
+ override fun toString(): String {
+ val builder = StringBuilder(19)
+ builder.append("[")
+ if (isBitOn(Calendar.MONDAY)) {
+ builder.append(if (builder.length > 1) " M" else "M")
+ }
+ if (isBitOn(Calendar.TUESDAY)) {
+ builder.append(if (builder.length > 1) " T" else "T")
+ }
+ if (isBitOn(Calendar.WEDNESDAY)) {
+ builder.append(if (builder.length > 1) " W" else "W")
+ }
+ if (isBitOn(Calendar.THURSDAY)) {
+ builder.append(if (builder.length > 1) " Th" else "Th")
+ }
+ if (isBitOn(Calendar.FRIDAY)) {
+ builder.append(if (builder.length > 1) " F" else "F")
+ }
+ if (isBitOn(Calendar.SATURDAY)) {
+ builder.append(if (builder.length > 1) " Sa" else "Sa")
+ }
+ if (isBitOn(Calendar.SUNDAY)) {
+ builder.append(if (builder.length > 1) " Su" else "Su")
+ }
+ builder.append("]")
+ return builder.toString()
+ }
+
+ /**
+ * @param context for accessing resources
+ * @param order the order in which to present the weekdays
+ * @return the enabled weekdays in the given `order`
+ */
+ fun toString(context: Context, order: Order): String {
+ return toString(context, order, false /* forceLongNames */)
+ }
+
+ /**
+ * @param context for accessing resources
+ * @param order the order in which to present the weekdays
+ * @return the enabled weekdays in the given `order` in a manner that
+ * is most appropriate for talk-back
+ */
+ fun toAccessibilityString(context: Context, order: Order): String {
+ return toString(context, order, true /* forceLongNames */)
+ }
+
+ @get:VisibleForTesting
+ val count: Int
+ get() {
+ var count = 0
+ for (calendarDay in Calendar.SUNDAY..Calendar.SATURDAY) {
+ if (isBitOn(calendarDay)) {
+ count++
+ }
+ }
+ return count
+ }
+
+ /**
+ * @param context for accessing resources
+ * @param order the order in which to present the weekdays
+ * @param forceLongNames if `true` the un-abbreviated weekdays are used
+ * @return the enabled weekdays in the given `order`
+ */
+ private fun toString(context: Context, order: Order, forceLongNames: Boolean): String {
+ if (!isRepeating) {
+ return ""
+ }
+
+ if (bits == ALL_DAYS) {
+ return context.getString(R.string.every_day)
+ }
+
+ val longNames = forceLongNames || count <= 1
+ val dfs = DateFormatSymbols()
+ val weekdays = if (longNames) dfs.weekdays else dfs.shortWeekdays
+
+ val separator: String = context.getString(R.string.day_concat)
+
+ val builder = StringBuilder(40)
+ for (calendarDay in order.calendarDays) {
+ if (isBitOn(calendarDay)) {
+ if (builder.isNotEmpty()) {
+ builder.append(separator)
+ }
+ builder.append(weekdays[calendarDay])
+ }
+ }
+ return builder.toString()
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/data/WidgetDAO.kt b/src/com/android/deskclock/data/WidgetDAO.kt
new file mode 100644
index 0000000..3259971
--- /dev/null
+++ b/src/com/android/deskclock/data/WidgetDAO.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source 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.deskclock.data
+
+import android.content.SharedPreferences
+
+/**
+ * This class encapsulates the transfer of data between widget objects and their permanent storage
+ * in [SharedPreferences].
+ */
+internal object WidgetDAO {
+ /** Suffix for a key to a preference that stores the instance count for a given widget type. */
+ private const val WIDGET_COUNT = "_widget_count"
+
+ /**
+ * @param widgetProviderClass indicates the type of widget being counted
+ * @param count the number of widgets of the given type
+ * @return the delta between the new count and the old count
+ */
+ fun updateWidgetCount(
+ prefs: SharedPreferences,
+ widgetProviderClass: Class<*>,
+ count: Int
+ ): Int {
+ val key = widgetProviderClass.simpleName + WIDGET_COUNT
+ val oldCount: Int = prefs.getInt(key, 0)
+ if (count == 0) {
+ prefs.edit().remove(key).apply()
+ } else {
+ prefs.edit().putInt(key, count).apply()
+ }
+ return count - oldCount
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/data/WidgetModel.kt b/src/com/android/deskclock/data/WidgetModel.kt
new file mode 100644
index 0000000..b63014f
--- /dev/null
+++ b/src/com/android/deskclock/data/WidgetModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source 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.deskclock.data
+
+import android.content.SharedPreferences
+import androidx.annotation.StringRes
+
+import com.android.deskclock.R
+import com.android.deskclock.events.Events
+
+/**
+ * All widget data is accessed via this model.
+ */
+internal class WidgetModel(private val mPrefs: SharedPreferences) {
+ /**
+ * @param widgetClass indicates the type of widget being counted
+ * @param count the number of widgets of the given type
+ * @param eventCategoryId identifies the category of event to send
+ */
+ fun updateWidgetCount(widgetClass: Class<*>, count: Int, @StringRes eventCategoryId: Int) {
+ var delta = WidgetDAO.updateWidgetCount(mPrefs, widgetClass, count)
+ while (delta > 0) {
+ Events.sendEvent(eventCategoryId, R.string.action_create, 0)
+ delta--
+ }
+ while (delta < 0) {
+ Events.sendEvent(eventCategoryId, R.string.action_delete, 0)
+ delta++
+ }
+ }
+}
\ No newline at end of file