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 }