1 /*
2  * Copyright (C) 2012 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 
17 package com.android.alarmclock;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.res.Resources;
22 import android.text.format.DateFormat;
23 import android.util.Log;
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.R;
30 import com.android.deskclock.data.City;
31 import com.android.deskclock.data.DataModel;
32 
33 import java.util.ArrayList;
34 import java.util.Calendar;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.TimeZone;
38 
39 import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
40 import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
41 import static com.android.deskclock.Utils.enforceMainLooper;
42 import static java.util.Calendar.DAY_OF_WEEK;
43 
44 public class DigitalWidgetViewsFactory implements RemoteViewsFactory {
45 
46     private static final String TAG = "DigWidgetViewsFactory";
47 
48     private final Intent mFillInIntent = new Intent();
49 
50     private final Context mContext;
51     private final Resources mResources;
52     private final float mFontSize;
53     private final float mFont24Size;
54     private final int mWidgetId;
55     private float mFontScale = 1;
56 
57     private City mHomeCity;
58     private List<City> mCities;
59     private boolean mShowHomeClock;
60 
DigitalWidgetViewsFactory(Context context, Intent intent)61     public DigitalWidgetViewsFactory(Context context, Intent intent) {
62         mContext = context;
63         mResources = context.getResources();
64         mFontSize = mResources.getDimension(R.dimen.widget_medium_font_size);
65         mFont24Size = mResources.getDimension(R.dimen.widget_24_medium_font_size);
66         mWidgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
67     }
68 
69     @Override
onCreate()70     public void onCreate() {
71         if (DigitalAppWidgetService.LOGGING) {
72             Log.i(TAG, "DigitalWidget onCreate " + mWidgetId);
73         }
74     }
75 
76     @Override
onDestroy()77     public void onDestroy() {
78         if (DigitalAppWidgetService.LOGGING) {
79             Log.i(TAG, "DigitalWidget onDestroy " + mWidgetId);
80         }
81     }
82 
83     /**
84      * <p>Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and
85      * mShowHomeClock.</p>
86      *
87      * {@inheritDoc}
88      */
89     @Override
getCount()90     public synchronized int getCount() {
91         if (!WidgetUtils.showList(mContext, mWidgetId, mFontScale)) {
92             return 0;
93         }
94 
95         final int homeClockCount = mShowHomeClock ? 1 : 0;
96         final int worldClockCount = mCities.size();
97         final double totalClockCount = homeClockCount + worldClockCount;
98 
99         // number of clocks / 2 clocks per row
100         return (int) Math.ceil(totalClockCount / 2);
101     }
102 
103     /**
104      * <p>Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and
105      * mShowHomeClock.</p>
106      *
107      * {@inheritDoc}
108      */
109     @Override
getViewAt(int position)110     public synchronized RemoteViews getViewAt(int position) {
111         final int homeClockOffset = mShowHomeClock ? -1 : 0;
112         final int leftIndex = position * 2 + homeClockOffset;
113         final int rightIndex = leftIndex + 1;
114 
115         final City left = leftIndex == -1 ? mHomeCity :
116                 (leftIndex < mCities.size() ? mCities.get(leftIndex) : null);
117         final City right = rightIndex < mCities.size() ? mCities.get(rightIndex) : null;
118 
119         final RemoteViews clock =
120                 new RemoteViews(mContext.getPackageName(), R.layout.world_clock_remote_list_item);
121 
122         // Show the left clock if one exists.
123         if (left != null) {
124             update(clock, left, R.id.left_clock, R.id.city_name_left, R.id.city_day_left);
125         } else {
126             hide(clock, R.id.left_clock, R.id.city_name_left, R.id.city_day_left);
127         }
128 
129         // Show the right clock if one exists.
130         if (right != null) {
131             update(clock, right, R.id.right_clock, R.id.city_name_right, R.id.city_day_right);
132         } else {
133             hide(clock, R.id.right_clock, R.id.city_name_right, R.id.city_day_right);
134         }
135 
136         // Hide last spacer in last row; show for all others.
137         final boolean lastRow = position == getCount() - 1;
138         clock.setViewVisibility(R.id.city_spacer, lastRow ? View.GONE : View.VISIBLE);
139 
140         clock.setOnClickFillInIntent(R.id.widget_item, mFillInIntent);
141         return clock;
142     }
143 
144     @Override
145     public long getItemId(int position) {
146         return position;
147     }
148 
149     @Override
150     public RemoteViews getLoadingView() {
151         return null;
152     }
153 
154     @Override
155     public int getViewTypeCount() {
156         return 1;
157     }
158 
159     @Override
160     public boolean hasStableIds() {
161         return true;
162     }
163 
164     /**
165      * <p>Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and
166      * mShowHomeClock.</p>
167      *
168      * {@inheritDoc}
169      */
170     @Override
171     public synchronized void onDataSetChanged() {
172         // Fetch the data on the main Looper.
173         final RefreshRunnable refreshRunnable = new RefreshRunnable();
174         DataModel.getDataModel().run(refreshRunnable);
175 
176         // Store the data in local variables.
177         mFontScale = WidgetUtils.getScaleRatio(mContext, null, mWidgetId);
178         mHomeCity = refreshRunnable.mHomeCity;
179         mCities = refreshRunnable.mCities;
180         mShowHomeClock = refreshRunnable.mShowHomeClock;
181     }
182 
183     private void update(RemoteViews clock, City city, int clockId, int labelId, int dayId) {
184         WidgetUtils.setTimeFormat(mContext, clock, true /* showAmPm */, clockId);
185 
186         final float fontSize = DateFormat.is24HourFormat(mContext) ? mFont24Size : mFontSize;
187         clock.setTextViewTextSize(clockId, TypedValue.COMPLEX_UNIT_PX, fontSize * mFontScale);
188         clock.setString(clockId, "setTimeZone", city.getTimeZoneId());
189         clock.setTextViewText(labelId, city.getName());
190 
191         // Compute if the city week day matches the weekday of the current timezone.
192         final Calendar localCal = Calendar.getInstance(TimeZone.getDefault());
193         final Calendar cityCal = Calendar.getInstance(city.getTimeZone());
194         final boolean displayDayOfWeek = localCal.get(DAY_OF_WEEK) != cityCal.get(DAY_OF_WEEK);
195 
196         // Bind the week day display.
197         if (displayDayOfWeek) {
198             final Locale locale = Locale.getDefault();
199             final String weekday = cityCal.getDisplayName(DAY_OF_WEEK, Calendar.SHORT, locale);
200             final String slashDay = mContext.getString(R.string.world_day_of_week_label, weekday);
201             clock.setTextViewText(dayId, slashDay);
202         }
203 
204         clock.setViewVisibility(dayId, displayDayOfWeek ? View.VISIBLE : View.GONE);
205         clock.setViewVisibility(clockId, View.VISIBLE);
206         clock.setViewVisibility(labelId, View.VISIBLE);
207     }
208 
209     private void hide(RemoteViews clock, int clockId, int labelId, int dayId) {
210         clock.setViewVisibility(dayId, View.INVISIBLE);
211         clock.setViewVisibility(clockId, View.INVISIBLE);
212         clock.setViewVisibility(labelId, View.INVISIBLE);
213     }
214 
215     /**
216      * This Runnable fetches data for this factory on the main thread to ensure all DataModel reads
217      * occur on the main thread.
218      */
219     private static final class RefreshRunnable implements Runnable {
220 
221         private City mHomeCity;
222         private List<City> mCities;
223         private boolean mShowHomeClock;
224 
225         @Override
226         public void run() {
227             enforceMainLooper();
228 
229             mHomeCity = DataModel.getDataModel().getHomeCity();
230             mCities = new ArrayList<>(DataModel.getDataModel().getSelectedCities());
231             mShowHomeClock = DataModel.getDataModel().getShowHomeClock();
232         }
233     }
234 }