| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.alarmclock; |
| |
| import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; |
| import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; |
| import static java.util.Calendar.DAY_OF_WEEK; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.text.format.DateFormat; |
| import android.util.TypedValue; |
| import android.view.View; |
| import android.widget.RemoteViews; |
| import android.widget.RemoteViewsService.RemoteViewsFactory; |
| |
| import com.android.deskclock.LogUtils; |
| import com.android.deskclock.R; |
| import com.android.deskclock.Utils; |
| import com.android.deskclock.data.City; |
| import com.android.deskclock.data.DataModel; |
| |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| |
| /** |
| * This factory produces entries in the world cities list view displayed at the bottom of the |
| * digital widget. Each row is comprised of two world cities located side-by-side. |
| */ |
| public class DigitalAppWidgetCityViewsFactory implements RemoteViewsFactory { |
| |
| private static final LogUtils.Logger LOGGER = new LogUtils.Logger("DigWidgetViewsFactory"); |
| |
| private final Intent mFillInIntent = new Intent(); |
| |
| private final Context mContext; |
| private final float m12HourFontSize; |
| private final float m24HourFontSize; |
| private final int mWidgetId; |
| private float mFontScale = 1; |
| |
| private City mHomeCity; |
| private boolean mShowHomeClock; |
| private List<City> mCities = Collections.emptyList(); |
| |
| public DigitalAppWidgetCityViewsFactory(Context context, Intent intent) { |
| mContext = context; |
| mWidgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID); |
| |
| final Resources res = context.getResources(); |
| m12HourFontSize = res.getDimension(R.dimen.digital_widget_city_12_medium_font_size); |
| m24HourFontSize = res.getDimension(R.dimen.digital_widget_city_24_medium_font_size); |
| } |
| |
| @Override |
| public void onCreate() { |
| LOGGER.i("DigitalAppWidgetCityViewsFactory onCreate " + mWidgetId); |
| } |
| |
| @Override |
| public void onDestroy() { |
| LOGGER.i("DigitalAppWidgetCityViewsFactory onDestroy " + mWidgetId); |
| } |
| |
| /** |
| * <p>Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and |
| * mShowHomeClock.</p> |
| * |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized int getCount() { |
| final int homeClockCount = mShowHomeClock ? 1 : 0; |
| final int worldClockCount = mCities.size(); |
| final double totalClockCount = homeClockCount + worldClockCount; |
| |
| // Number of clocks / 2 clocks per row |
| return (int) Math.ceil(totalClockCount / 2); |
| } |
| |
| /** |
| * <p>Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and |
| * mShowHomeClock.</p> |
| * |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized RemoteViews getViewAt(int position) { |
| final int homeClockOffset = mShowHomeClock ? -1 : 0; |
| final int leftIndex = position * 2 + homeClockOffset; |
| final int rightIndex = leftIndex + 1; |
| |
| final City left = leftIndex == -1 ? mHomeCity : |
| (leftIndex < mCities.size() ? mCities.get(leftIndex) : null); |
| final City right = rightIndex < mCities.size() ? mCities.get(rightIndex) : null; |
| |
| String packageName = mContext.getPackageName(); |
| int[] layoutIds = WidgetUtils.getWidgetLayouts(mContext, mWidgetId); |
| final RemoteViews rv = new RemoteViews(packageName, layoutIds[0]); |
| rv.setLightBackgroundLayoutId(layoutIds[1]); |
| |
| rv.removeAllViews(R.id.themed_root); |
| rv.addView(R.id.themed_root, new RemoteViews(packageName, |
| R.layout.world_clock_remote_list_item)); |
| |
| // Show the left clock if one exists. |
| if (left != null) { |
| update(rv, left, R.id.left_clock, R.id.city_name_left, R.id.city_day_left); |
| } else { |
| hide(rv, R.id.left_clock, R.id.city_name_left, R.id.city_day_left); |
| } |
| |
| // Show the right clock if one exists. |
| if (right != null) { |
| update(rv, right, R.id.right_clock, R.id.city_name_right, R.id.city_day_right); |
| } else { |
| hide(rv, R.id.right_clock, R.id.city_name_right, R.id.city_day_right); |
| } |
| |
| // Hide last spacer in last row; show for all others. |
| final boolean lastRow = position == getCount() - 1; |
| rv.setViewVisibility(R.id.city_spacer, lastRow ? View.GONE : View.VISIBLE); |
| |
| rv.setOnClickFillInIntent(R.id.widget_item, mFillInIntent); |
| return rv; |
| } |
| |
| @Override |
| public long getItemId(int position) { |
| return position; |
| } |
| |
| @Override |
| public RemoteViews getLoadingView() { |
| return null; |
| } |
| |
| @Override |
| public int getViewTypeCount() { |
| return 1; |
| } |
| |
| @Override |
| public boolean hasStableIds() { |
| return false; |
| } |
| |
| /** |
| * <p>Synchronized to ensure single-threaded reading/writing of mCities, mHomeCity and |
| * mShowHomeClock.</p> |
| * |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized void onDataSetChanged() { |
| // Fetch the data on the main Looper. |
| final RefreshRunnable refreshRunnable = new RefreshRunnable(); |
| DataModel.getDataModel().run(refreshRunnable); |
| |
| // Store the data in local variables. |
| mHomeCity = refreshRunnable.mHomeCity; |
| mCities = refreshRunnable.mCities; |
| mShowHomeClock = refreshRunnable.mShowHomeClock; |
| mFontScale = WidgetUtils.getScaleRatio(mContext, null, mWidgetId, mCities.size()); |
| } |
| |
| private void update(RemoteViews rv, City city, int clockId, int labelId, int dayId) { |
| rv.setCharSequence(clockId, "setFormat12Hour", Utils.get12ModeFormat(0.4f, false)); |
| rv.setCharSequence(clockId, "setFormat24Hour", Utils.get24ModeFormat(false)); |
| |
| final boolean is24HourFormat = DateFormat.is24HourFormat(mContext); |
| final float fontSize = is24HourFormat ? m24HourFontSize : m12HourFontSize; |
| rv.setTextViewTextSize(clockId, TypedValue.COMPLEX_UNIT_PX, fontSize * mFontScale); |
| rv.setString(clockId, "setTimeZone", city.getTimeZone().getID()); |
| rv.setTextViewText(labelId, city.getName()); |
| |
| // Compute if the city week day matches the weekday of the current timezone. |
| final Calendar localCal = Calendar.getInstance(TimeZone.getDefault()); |
| final Calendar cityCal = Calendar.getInstance(city.getTimeZone()); |
| final boolean displayDayOfWeek = localCal.get(DAY_OF_WEEK) != cityCal.get(DAY_OF_WEEK); |
| |
| // Bind the week day display. |
| if (displayDayOfWeek) { |
| final Locale locale = Locale.getDefault(); |
| final String weekday = cityCal.getDisplayName(DAY_OF_WEEK, Calendar.SHORT, locale); |
| final String slashDay = mContext.getString(R.string.world_day_of_week_label, weekday); |
| rv.setTextViewText(dayId, slashDay); |
| } |
| |
| rv.setViewVisibility(dayId, displayDayOfWeek ? View.VISIBLE : View.GONE); |
| rv.setViewVisibility(clockId, View.VISIBLE); |
| rv.setViewVisibility(labelId, View.VISIBLE); |
| } |
| |
| private void hide(RemoteViews clock, int clockId, int labelId, int dayId) { |
| clock.setViewVisibility(dayId, View.INVISIBLE); |
| clock.setViewVisibility(clockId, View.INVISIBLE); |
| clock.setViewVisibility(labelId, View.INVISIBLE); |
| } |
| |
| /** |
| * This Runnable fetches data for this factory on the main thread to ensure all DataModel reads |
| * occur on the main thread. |
| */ |
| private static final class RefreshRunnable implements Runnable { |
| private City mHomeCity; |
| private List<City> mCities; |
| private boolean mShowHomeClock; |
| |
| @Override |
| public void run() { |
| mHomeCity = DataModel.getDataModel().getHomeCity(); |
| mCities = new ArrayList<>(DataModel.getDataModel().getSelectedCities()); |
| mShowHomeClock = DataModel.getDataModel().getShowHomeClock(); |
| } |
| } |
| } |