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