Home | History | Annotate | Download | only in alarmclock
      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