Restore world cities list to digital clock widget

Bug: 28638735

Change-Id: Ia7dfe3bc6b64df2db4c2971a5dc9913a31a41b88
diff --git a/src/com/android/alarmclock/CityAppWidgetProvider.java b/src/com/android/alarmclock/CityAppWidgetProvider.java
index abcb5fe..9a911b2 100644
--- a/src/com/android/alarmclock/CityAppWidgetProvider.java
+++ b/src/com/android/alarmclock/CityAppWidgetProvider.java
@@ -33,7 +33,6 @@
 import android.view.View;
 import android.widget.RemoteViews;
 import android.widget.TextClock;
-import android.widget.TextView;
 
 import com.android.deskclock.DeskClock;
 import com.android.deskclock.LogUtils;
@@ -209,10 +208,9 @@
         final int maxHeightPx = (int) (density * options.getInt(OPTION_APPWIDGET_MAX_HEIGHT));
         final int targetWidthPx = portrait ? minWidthPx : maxWidthPx;
         final int targetHeightPx = portrait ? maxHeightPx : minHeightPx;
-        final int fontSizePx = resources.getDimensionPixelSize(R.dimen.city_widget_name_font_size);
         final int largestClockFontSizePx =
                 resources.getDimensionPixelSize(R.dimen.widget_max_clock_font_size);
-        final Sizes template = new Sizes(city, targetWidthPx, targetHeightPx, fontSizePx,
+        final Sizes template = new Sizes(city, targetWidthPx, targetHeightPx,
                 largestClockFontSizePx);
 
         // Create a remote view for the city widget.
@@ -242,8 +240,6 @@
 
         // Apply the computed sizes to the remote views.
         rv.setTextViewTextSize(R.id.clock, COMPLEX_UNIT_PX, sizes.mClockFontSizePx);
-        rv.setTextViewTextSize(R.id.city_day, COMPLEX_UNIT_PX, sizes.mFontSizePx);
-        rv.setTextViewTextSize(R.id.city_name, COMPLEX_UNIT_PX, sizes.mFontSizePx);
         rv.setInt(R.id.city_name, "setMaxWidth", sizes.mCityNameMaxWidthPx);
         return rv;
     }
@@ -341,14 +337,11 @@
         // Configure the clock to display the time string.
         final TextClock clock = (TextClock) sizer.findViewById(R.id.clock);
         final TextClock cityDay = (TextClock) sizer.findViewById(R.id.city_day);
-        final TextView cityName = (TextView) sizer.findViewById(R.id.city_name);
 
         // Adjust the font sizes.
         measuredSizes.setClockFontSizePx(clockFontSizePx);
         clock.setText(time);
         clock.setTextSize(COMPLEX_UNIT_PX, measuredSizes.mClockFontSizePx);
-        cityDay.setTextSize(COMPLEX_UNIT_PX, measuredSizes.mFontSizePx);
-        cityName.setTextSize(COMPLEX_UNIT_PX, measuredSizes.mFontSizePx);
 
         // Measure and layout the sizer.
         final int widthSize = View.MeasureSpec.getSize(measuredSizes.mTargetWidthPx);
@@ -455,21 +448,17 @@
         private int mMeasuredTextClockWidthPx;
         private int mMeasuredTextClockHeightPx;
 
-        /** The size of the font to use on the city name / day of week fields. */
-        private final int mFontSizePx;
-
         /** The size of the font to use on the clock field. */
         private int mClockFontSizePx;
 
         /** If the city name requires more width that this threshold the text is elided. */
         private int mCityNameMaxWidthPx;
 
-        private Sizes(City city, int targetWidthPx, int targetHeightPx, int fontSizePx,
+        private Sizes(City city, int targetWidthPx, int targetHeightPx,
                 int largestClockFontSizePx) {
             mCity = city;
             mTargetWidthPx = targetWidthPx;
             mTargetHeightPx = targetHeightPx;
-            mFontSizePx = fontSizePx;
             mLargestClockFontSizePx = largestClockFontSizePx;
             mSmallestClockFontSizePx = 0;
         }
@@ -486,8 +475,7 @@
         }
 
         private Sizes newSize() {
-            return new Sizes(mCity, mTargetWidthPx, mTargetHeightPx, mFontSizePx,
-                    mLargestClockFontSizePx);
+            return new Sizes(mCity, mTargetWidthPx, mTargetHeightPx, mLargestClockFontSizePx);
         }
 
         @Override
diff --git a/src/com/android/alarmclock/DigitalAppWidgetCityService.java b/src/com/android/alarmclock/DigitalAppWidgetCityService.java
new file mode 100644
index 0000000..6392697
--- /dev/null
+++ b/src/com/android/alarmclock/DigitalAppWidgetCityService.java
@@ -0,0 +1,28 @@
+/*
+ * 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 android.content.Intent;
+import android.widget.RemoteViewsService;
+
+public class DigitalAppWidgetCityService extends RemoteViewsService {
+
+    @Override
+    public RemoteViewsFactory onGetViewFactory(Intent i) {
+        return new DigitalAppWidgetCityViewsFactory(getApplicationContext(), i);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.java b/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.java
new file mode 100644
index 0000000..00e3a00
--- /dev/null
+++ b/src/com/android/alarmclock/DigitalAppWidgetCityViewsFactory.java
@@ -0,0 +1,231 @@
+/*
+ * 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 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;
+
+import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
+import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
+import static java.util.Calendar.DAY_OF_WEEK;
+
+/**
+ * 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;
+
+        final RemoteViews clock =
+                new RemoteViews(mContext.getPackageName(), R.layout.world_clock_remote_list_item);
+
+        // Show the left clock if one exists.
+        if (left != null) {
+            update(clock, left, R.id.left_clock, R.id.city_name_left, R.id.city_day_left);
+        } else {
+            hide(clock, 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(clock, right, R.id.right_clock, R.id.city_name_right, R.id.city_day_right);
+        } else {
+            hide(clock, 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;
+        clock.setViewVisibility(R.id.city_spacer, lastRow ? View.GONE : View.VISIBLE);
+
+        clock.setOnClickFillInIntent(R.id.widget_item, mFillInIntent);
+        return clock;
+    }
+
+    @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 true;
+    }
+
+    /**
+     * <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));
+        rv.setCharSequence(clockId, "setFormat24Hour", Utils.get24ModeFormat());
+
+        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();
+        }
+    }
+}
diff --git a/src/com/android/alarmclock/DigitalAppWidgetProvider.java b/src/com/android/alarmclock/DigitalAppWidgetProvider.java
index a2c683b..cea5aee 100644
--- a/src/com/android/alarmclock/DigitalAppWidgetProvider.java
+++ b/src/com/android/alarmclock/DigitalAppWidgetProvider.java
@@ -17,6 +17,7 @@
 package com.android.alarmclock;
 
 import android.annotation.SuppressLint;
+import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProvider;
@@ -26,10 +27,12 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Typeface;
+import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
+import android.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -40,12 +43,20 @@
 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 com.android.deskclock.worldclock.CitySelectionActivity;
 
 import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
 import java.util.Locale;
+import java.util.Set;
+import java.util.TimeZone;
 
 import static android.app.AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED;
+import static android.app.PendingIntent.FLAG_NO_CREATE;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
 import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
 import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
@@ -59,6 +70,7 @@
 import static android.view.View.MeasureSpec.UNSPECIFIED;
 import static android.view.View.VISIBLE;
 import static com.android.deskclock.alarms.AlarmStateManager.SYSTEM_ALARM_CHANGE_ACTION;
+import static com.android.deskclock.data.DataModel.ACTION_WORLD_CITIES_CHANGED;
 import static java.lang.Math.max;
 import static java.lang.Math.round;
 
@@ -85,10 +97,36 @@
 
     private static final LogUtils.Logger LOGGER = new LogUtils.Logger("DigitalWidgetProvider");
 
+    /**
+     * Intent action used for refreshing a world city display when any of them changes days or when
+     * the default TimeZone changes days. This affects the widget display because the day-of-week is
+     * only visible when the world city day-of-week differs from the default TimeZone's day-of-week.
+     */
+    private static final String ACTION_ON_DAY_CHANGE = "com.android.deskclock.ON_DAY_CHANGE";
+
+    /** Intent used to deliver the {@link #ACTION_ON_DAY_CHANGE} callback. */
+    private static final Intent DAY_CHANGE_INTENT = new Intent(ACTION_ON_DAY_CHANGE);
+
     /** A custom font containing a special glyph that draws a clock icon with proper drop shadow. */
     private static Typeface sAlarmIconTypeface;
 
     @Override
+    public void onEnabled(Context context) {
+        super.onEnabled(context);
+
+        // Schedule the day-change callback if necessary.
+        updateDayChangeCallback(context);
+    }
+
+    @Override
+    public void onDisabled(Context context) {
+        super.onDisabled(context);
+
+        // Remove any scheduled day-change callback.
+        removeDayChangeCallback(context);
+    }
+
+    @Override
     public void onReceive(@NonNull Context context, @NonNull Intent intent) {
         LOGGER.i("Digital Widget processing action %s", intent.getAction());
         super.onReceive(context, intent);
@@ -105,8 +143,10 @@
             case ACTION_SCREEN_ON:
             case ACTION_DATE_CHANGED:
             case ACTION_LOCALE_CHANGED:
+            case ACTION_ON_DAY_CHANGE:
             case ACTION_TIMEZONE_CHANGED:
             case SYSTEM_ALARM_CHANGE_ACTION:
+            case ACTION_WORLD_CITIES_CHANGED:
             case ACTION_NEXT_ALARM_CLOCK_CHANGED:
                 for (int widgetId : widgetIds) {
                     relayoutWidget(context, wm, widgetId, wm.getAppWidgetOptions(widgetId));
@@ -115,6 +155,8 @@
 
         final DataModel dm = DataModel.getDataModel();
         dm.updateWidgetCount(getClass(), widgetIds.length, R.string.category_digital_widget);
+
+        updateDayChangeCallback(context);
     }
 
     /**
@@ -151,6 +193,7 @@
         final RemoteViews landscape = relayoutWidget(context, wm, widgetId, options, false);
         final RemoteViews widget = new RemoteViews(landscape, portrait);
         wm.updateAppWidget(widgetId, widget);
+        wm.notifyAppWidgetViewDataChanged(widgetId, R.id.world_city_list);
     }
 
     /**
@@ -214,6 +257,28 @@
         rv.setTextViewTextSize(R.id.date, COMPLEX_UNIT_PX, sizes.mFontSizePx);
         rv.setTextViewTextSize(R.id.nextAlarm, COMPLEX_UNIT_PX, sizes.mFontSizePx);
         rv.setTextViewTextSize(R.id.clock, COMPLEX_UNIT_PX, sizes.mClockFontSizePx);
+
+        final int smallestWorldCityListSizePx =
+                resources.getDimensionPixelSize(R.dimen.widget_min_world_city_list_size);
+        if (sizes.getListHeight() <= smallestWorldCityListSizePx) {
+            // Insufficient space; hide the world city list.
+            rv.setViewVisibility(R.id.world_city_list, GONE);
+        } else {
+            // Set an adapter on the world city list. That adapter connects to a Service via intent.
+            final Intent intent = new Intent(context, DigitalAppWidgetCityService.class);
+            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
+            rv.setRemoteAdapter(R.id.world_city_list, intent);
+            rv.setViewVisibility(R.id.world_city_list, VISIBLE);
+
+            // Tapping on the widget opens the city selection activity (if not on the lock screen).
+            if (Utils.isWidgetClickable(wm, widgetId)) {
+                final Intent selectCity = new Intent(context, CitySelectionActivity.class);
+                final PendingIntent pi = PendingIntent.getActivity(context, 0, selectCity, 0);
+                rv.setPendingIntentTemplate(R.id.world_city_list, pi);
+            }
+        }
+
         return rv;
     }
 
@@ -277,6 +342,48 @@
     }
 
     /**
+     * Remove the existing day-change callback if it is not needed (no selected cities exist).
+     * Add the day-change callback if it is needed (selected cities exist).
+     */
+    private void updateDayChangeCallback(Context context) {
+        final List<City> selectedCities = DataModel.getDataModel().getSelectedCities();
+        if (selectedCities.isEmpty()) {
+            // Remove the existing day-change callback.
+            removeDayChangeCallback(context);
+            return;
+        }
+
+        // Look up the time at which the next day change occurs across all timezones.
+        final Set<TimeZone> zones = new ArraySet<>(selectedCities.size() + 1);
+        zones.add(TimeZone.getDefault());
+        for (City city : selectedCities) {
+            zones.add(city.getTimeZone());
+        }
+        final Date nextDay = Utils.getNextDay(new Date(), zones);
+
+        // Schedule the next day-change callback; at least one city is displayed.
+        final PendingIntent pi =
+                PendingIntent.getBroadcast(context, 0, DAY_CHANGE_INTENT, FLAG_UPDATE_CURRENT);
+        getAlarmManager(context).setExact(AlarmManager.RTC, nextDay.getTime(), pi);
+    }
+
+    /**
+     * Remove the existing day-change callback.
+     */
+    private void removeDayChangeCallback(Context context) {
+        final PendingIntent pi =
+                PendingIntent.getBroadcast(context, 0, DAY_CHANGE_INTENT, FLAG_NO_CREATE);
+        if (pi != null) {
+            getAlarmManager(context).cancel(pi);
+            pi.cancel();
+        }
+    }
+
+    private static AlarmManager getAlarmManager(Context context) {
+        return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+    }
+
+    /**
      * Compute all font and icon sizes based on the given {@code clockFontSize} and apply them to
      * the offscreen {@code sizer} view. Measure the {@code sizer} view and return the resulting
      * size measurements.
@@ -399,6 +506,13 @@
             mIconPaddingPx = mFontSizePx / 3;
         }
 
+        /**
+         * @return the amount of widget height available to the world cities list
+         */
+        private int getListHeight() {
+            return mTargetHeightPx - mMeasuredHeightPx;
+        }
+
         private boolean hasViolations() {
             return mMeasuredWidthPx > mTargetWidthPx || mMeasuredHeightPx > mTargetHeightPx;
         }
diff --git a/src/com/android/alarmclock/WidgetUtils.java b/src/com/android/alarmclock/WidgetUtils.java
new file mode 100644
index 0000000..e3099cc
--- /dev/null
+++ b/src/com/android/alarmclock/WidgetUtils.java
@@ -0,0 +1,95 @@
+/*
+ * 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 android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+
+import com.android.deskclock.R;
+
+public final class WidgetUtils {
+
+    private WidgetUtils() {}
+
+    // Calculate the scale factor of the fonts in the widget
+    public static float getScaleRatio(Context context, Bundle options, int id, int cityCount) {
+        if (options == null) {
+            AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
+            if (widgetManager == null) {
+                // no manager , do no scaling
+                return 1f;
+            }
+            options = widgetManager.getAppWidgetOptions(id);
+        }
+        if (options != null) {
+            int minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
+            if (minWidth == 0) {
+                // No data , do no scaling
+                return 1f;
+            }
+            Resources res = context.getResources();
+            float density = res.getDisplayMetrics().density;
+            float ratio = (density * minWidth) / res.getDimension(R.dimen.min_digital_widget_width);
+            ratio = Math.min(ratio, getHeightScaleRatio(context, options, id));
+            ratio *= .83f;
+
+            if (cityCount > 0) {
+                return (ratio > 1f) ? 1f : ratio;
+            }
+
+            ratio = Math.min(ratio, 1.6f);
+            if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+                ratio = Math.max(ratio, .71f);
+            }
+            else {
+                ratio = Math.max(ratio, .45f);
+            }
+            return ratio;
+        }
+        return 1f;
+    }
+
+    // Calculate the scale factor of the fonts in the list of  the widget using the widget height
+    private static float getHeightScaleRatio(Context context, Bundle options, int id) {
+        if (options == null) {
+            AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
+            if (widgetManager == null) {
+                // no manager , do no scaling
+                return 1f;
+            }
+            options = widgetManager.getAppWidgetOptions(id);
+        }
+        if (options != null) {
+            int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+            if (minHeight == 0) {
+                // No data , do no scaling
+                return 1f;
+            }
+            Resources res = context.getResources();
+            float density = res.getDisplayMetrics().density;
+            float ratio = density * minHeight / res.getDimension(R.dimen.min_digital_widget_height);
+            if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+                return ratio * 1.75f;
+            }
+            return ratio;
+        }
+        return 1;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/data/CityModel.java b/src/com/android/deskclock/data/CityModel.java
index 195189f..2f344ad 100644
--- a/src/com/android/deskclock/data/CityModel.java
+++ b/src/com/android/deskclock/data/CityModel.java
@@ -185,6 +185,9 @@
         mAllCities = null;
         mSelectedCities = null;
         mUnselectedCities = null;
+
+        // Broadcast the change to the selected cities for the benefit of widgets.
+        sendCitiesChangedBroadcast();
     }
 
     /**
@@ -242,6 +245,10 @@
         throw new IllegalStateException("unexpected city sort: " + citySort);
     }
 
+    private void sendCitiesChangedBroadcast() {
+        mContext.sendBroadcast(new Intent(DataModel.ACTION_WORLD_CITIES_CHANGED));
+    }
+
     /**
      * Cached information that is locale-sensitive must be cleared in response to locale changes.
      */
@@ -266,6 +273,9 @@
             switch (key) {
                 case SettingsActivity.KEY_HOME_TZ:
                     mHomeCity = null;
+                case SettingsActivity.KEY_AUTO_HOME_CLOCK:
+                    sendCitiesChangedBroadcast();
+                    break;
             }
         }
     }
diff --git a/src/com/android/deskclock/data/DataModel.java b/src/com/android/deskclock/data/DataModel.java
index ad0e226..39bbe0d 100644
--- a/src/com/android/deskclock/data/DataModel.java
+++ b/src/com/android/deskclock/data/DataModel.java
@@ -40,6 +40,9 @@
     /** Indicates the preferred sort order of cities. */
     public enum CitySort {NAME, UTC_OFFSET}
 
+    public static final String ACTION_WORLD_CITIES_CHANGED =
+            "com.android.deskclock.WORLD_CITIES_CHANGED";
+
     /** The single instance of this data model that exists for the life of the application. */
     private static final DataModel sDataModel = new DataModel();