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 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 75 public void onCreate() { 76 LOGGER.i("DigitalAppWidgetCityViewsFactory onCreate " + mWidgetId); 77 } 78 79 @Override 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 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 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