• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.alarmclock
17 
18 import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
19 import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
20 import android.content.Context
21 import android.content.Intent
22 import android.content.res.Resources
23 import android.text.format.DateFormat
24 import android.util.TypedValue
25 import android.view.View
26 import android.widget.RemoteViews
27 import android.widget.RemoteViewsService.RemoteViewsFactory
28 
29 import com.android.deskclock.LogUtils
30 import com.android.deskclock.R
31 import com.android.deskclock.Utils
32 import com.android.deskclock.data.City
33 import com.android.deskclock.data.DataModel
34 
35 import java.util.ArrayList
36 import java.util.Calendar
37 import java.util.Locale
38 import java.util.TimeZone
39 
40 /**
41  * This factory produces entries in the world cities list view displayed at the bottom of the
42  * digital widget. Each row is comprised of two world cities located side-by-side.
43  */
44 class DigitalAppWidgetCityViewsFactory(context: Context, intent: Intent) : RemoteViewsFactory {
45     private val mFillInIntent: Intent = Intent()
46     private val mContext: Context = context
47     private val m12HourFontSize: Float
48     private val m24HourFontSize: Float
49     private val mWidgetId: Int = intent.getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID)
50     private var mFontScale = 1f
51     private var mHomeCity: City? = null
52     private var mShowHomeClock = false
53     private var mCities: List<City>? = emptyList()
54 
55     init {
56         val res: Resources = context.getResources()
57         m12HourFontSize = res.getDimension(R.dimen.digital_widget_city_12_medium_font_size)
58         m24HourFontSize = res.getDimension(R.dimen.digital_widget_city_24_medium_font_size)
59     }
60 
onCreatenull61     override fun onCreate() {
62         LOGGER.i("DigitalAppWidgetCityViewsFactory onCreate $mWidgetId")
63     }
64 
onDestroynull65     override fun onDestroy() {
66         LOGGER.i("DigitalAppWidgetCityViewsFactory onDestroy $mWidgetId")
67     }
68 
69     /**
70      * Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and
71      * mShowHomeClock.
72      *
73      * {@inheritDoc}
74      */
75     @Synchronized
getCountnull76     override fun getCount(): Int {
77         val homeClockCount = if (mShowHomeClock) 1 else 0
78         val worldClockCount = mCities!!.size
79         val totalClockCount = homeClockCount + worldClockCount.toDouble()
80 
81         // Number of clocks / 2 clocks per row
82         return Math.ceil(totalClockCount / 2).toInt()
83     }
84 
85     /**
86      * Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and
87      * mShowHomeClock.
88      *
89      * {@inheritDoc}
90      */
91     @Synchronized
getViewAtnull92     override fun getViewAt(position: Int): RemoteViews {
93         val homeClockOffset = if (mShowHomeClock) -1 else 0
94         val leftIndex = position * 2 + homeClockOffset
95         val rightIndex = leftIndex + 1
96         val left = when {
97             leftIndex == -1 -> mHomeCity
98             leftIndex < mCities!!.size -> mCities!![leftIndex]
99             else -> null
100         }
101         val right = if (rightIndex < mCities!!.size) mCities!![rightIndex] else null
102         val rv = RemoteViews(mContext.getPackageName(), R.layout.world_clock_remote_list_item)
103 
104         // Show the left clock if one exists.
105         if (left != null) {
106             update(rv, left, R.id.left_clock, R.id.city_name_left, R.id.city_day_left)
107         } else {
108             hide(rv, R.id.left_clock, R.id.city_name_left, R.id.city_day_left)
109         }
110 
111         // Show the right clock if one exists.
112         if (right != null) {
113             update(rv, right, R.id.right_clock, R.id.city_name_right, R.id.city_day_right)
114         } else {
115             hide(rv, R.id.right_clock, R.id.city_name_right, R.id.city_day_right)
116         }
117 
118         // Hide last spacer in last row; show for all others.
119         val lastRow = position == count - 1
120         rv.setViewVisibility(R.id.city_spacer, if (lastRow) View.GONE else View.VISIBLE)
121         rv.setOnClickFillInIntent(R.id.widget_item, mFillInIntent)
122         return rv
123     }
124 
getItemIdnull125     override fun getItemId(position: Int): Long {
126         return position.toLong()
127     }
128 
getLoadingViewnull129     override fun getLoadingView(): RemoteViews? {
130         return null
131     }
132 
getViewTypeCountnull133     override fun getViewTypeCount(): Int {
134         return 1
135     }
136 
hasStableIdsnull137     override fun hasStableIds(): Boolean {
138         return false
139     }
140 
141     /**
142      * Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and
143      * mShowHomeClock.
144      *
145      * {@inheritDoc}
146      */
147     @Synchronized
onDataSetChangednull148     override fun onDataSetChanged() {
149         // Fetch the data on the main Looper.
150         val refreshRunnable = RefreshRunnable()
151         DataModel.dataModel.run(refreshRunnable)
152 
153         // Store the data in local variables.
154         mHomeCity = refreshRunnable.mHomeCity
155         mCities = refreshRunnable.mCities
156         mShowHomeClock = refreshRunnable.mShowHomeClock
157         mFontScale = WidgetUtils.getScaleRatio(mContext, null, mWidgetId, mCities!!.size)
158     }
159 
updatenull160     private fun update(rv: RemoteViews, city: City, clockId: Int, labelId: Int, dayId: Int) {
161         rv.setCharSequence(clockId, "setFormat12Hour", Utils.get12ModeFormat(0.4f, false))
162         rv.setCharSequence(clockId, "setFormat24Hour", Utils.get24ModeFormat(false))
163 
164         val is24HourFormat: Boolean = DateFormat.is24HourFormat(mContext)
165         val fontSize = if (is24HourFormat) m24HourFontSize else m12HourFontSize
166         rv.setTextViewTextSize(clockId, TypedValue.COMPLEX_UNIT_PX, fontSize * mFontScale)
167         rv.setString(clockId, "setTimeZone", city.timeZone.id)
168         rv.setTextViewText(labelId, city.name)
169 
170         // Compute if the city week day matches the weekday of the current timezone.
171         val localCal = Calendar.getInstance(TimeZone.getDefault())
172         val cityCal = Calendar.getInstance(city.timeZone)
173         val displayDayOfWeek = localCal[Calendar.DAY_OF_WEEK] != cityCal[Calendar.DAY_OF_WEEK]
174 
175         // Bind the week day display.
176         if (displayDayOfWeek) {
177             val locale = Locale.getDefault()
178             val weekday = cityCal.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, locale)
179             val slashDay: String = mContext.getString(R.string.world_day_of_week_label, weekday)
180             rv.setTextViewText(dayId, slashDay)
181         }
182 
183         rv.setViewVisibility(dayId, if (displayDayOfWeek) View.VISIBLE else View.GONE)
184         rv.setViewVisibility(clockId, View.VISIBLE)
185         rv.setViewVisibility(labelId, View.VISIBLE)
186     }
187 
hidenull188     private fun hide(clock: RemoteViews, clockId: Int, labelId: Int, dayId: Int) {
189         clock.setViewVisibility(dayId, View.INVISIBLE)
190         clock.setViewVisibility(clockId, View.INVISIBLE)
191         clock.setViewVisibility(labelId, View.INVISIBLE)
192     }
193 
194     /**
195      * This Runnable fetches data for this factory on the main thread to ensure all DataModel reads
196      * occur on the main thread.
197      */
198     private class RefreshRunnable : Runnable {
199         var mHomeCity: City? = null
200         var mCities: List<City>? = null
201         var mShowHomeClock = false
202 
runnull203         override fun run() {
204             mHomeCity = DataModel.dataModel.homeCity
205             mCities = ArrayList(DataModel.dataModel.selectedCities)
206             mShowHomeClock = DataModel.dataModel.showHomeClock
207         }
208     }
209 
210     companion object {
211         private val LOGGER = LogUtils.Logger("DigWidgetViewsFactory")
212     }
213 }
214