| /* |
| * Copyright (C) 2012 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.app.AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED; |
| import static android.app.PendingIntent.FLAG_IMMUTABLE; |
| 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; |
| import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH; |
| import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; |
| import static android.content.Intent.ACTION_LOCALE_CHANGED; |
| import static android.content.Intent.ACTION_TIMEZONE_CHANGED; |
| import static android.content.Intent.ACTION_TIME_CHANGED; |
| import static android.util.TypedValue.COMPLEX_UNIT_PX; |
| import static android.view.View.GONE; |
| import static android.view.View.MeasureSpec.UNSPECIFIED; |
| import static android.view.View.VISIBLE; |
| import static com.android.deskclock.data.DataModel.ACTION_WORLD_CITIES_CHANGED; |
| import static java.lang.Math.max; |
| import static java.lang.Math.round; |
| |
| import android.annotation.SuppressLint; |
| import android.app.AlarmManager; |
| import android.app.PendingIntent; |
| import android.appwidget.AppWidgetManager; |
| import android.appwidget.AppWidgetProvider; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.res.Resources; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.text.TextUtils; |
| import android.text.format.DateFormat; |
| import android.util.ArraySet; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.widget.LinearLayout; |
| import android.widget.RemoteViews; |
| import android.widget.TextClock; |
| import android.widget.TextView; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.deskclock.DeskClock; |
| 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.uidata.UiDataModel; |
| 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; |
| |
| /** |
| * <p>This provider produces a widget resembling one of the formats below.</p> |
| * |
| * If an alarm is scheduled to ring in the future: |
| * <pre> |
| * 12:59 AM |
| * WED, FEB 3 ⏰ THU 9:30 AM |
| * </pre> |
| * |
| * If no alarm is scheduled to ring in the future: |
| * <pre> |
| * 12:59 AM |
| * WED, FEB 3 |
| * </pre> |
| * |
| * This widget is scaling the font sizes to fit within the widget bounds chosen by the user without |
| * any clipping. To do so it measures layouts offscreen using a range of font sizes in order to |
| * choose optimal values. |
| */ |
| public class DigitalAppWidgetProvider extends AppWidgetProvider { |
| |
| private static final LogUtils.Logger LOGGER = new LogUtils.Logger("DigitalWidgetProvider"); |
| |
| private static boolean sReceiversRegistered; |
| |
| /** |
| * 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); |
| |
| @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("onReceive: " + intent); |
| super.onReceive(context, intent); |
| |
| final AppWidgetManager wm = AppWidgetManager.getInstance(context); |
| if (wm == null) { |
| return; |
| } |
| |
| final ComponentName provider = new ComponentName(context, getClass()); |
| final int[] widgetIds = wm.getAppWidgetIds(provider); |
| |
| final String action = intent.getAction(); |
| switch (action) { |
| case ACTION_NEXT_ALARM_CLOCK_CHANGED: |
| case ACTION_LOCALE_CHANGED: |
| case ACTION_TIME_CHANGED: |
| case ACTION_TIMEZONE_CHANGED: |
| case ACTION_ON_DAY_CHANGE: |
| case ACTION_WORLD_CITIES_CHANGED: |
| case ACTION_CONFIGURATION_CHANGED: |
| for (int widgetId : widgetIds) { |
| relayoutWidget(context, wm, widgetId, wm.getAppWidgetOptions(widgetId)); |
| } |
| } |
| |
| final DataModel dm = DataModel.getDataModel(); |
| dm.updateWidgetCount(getClass(), widgetIds.length, R.string.category_digital_widget); |
| |
| if (widgetIds.length > 0) { |
| updateDayChangeCallback(context); |
| } |
| } |
| |
| /** |
| * Called when widgets must provide remote views. |
| */ |
| @Override |
| public void onUpdate(Context context, AppWidgetManager wm, int[] widgetIds) { |
| super.onUpdate(context, wm, widgetIds); |
| registerReceivers(context, this); |
| |
| for (int widgetId : widgetIds) { |
| relayoutWidget(context, wm, widgetId, wm.getAppWidgetOptions(widgetId)); |
| } |
| } |
| |
| private static void registerReceivers(Context context, BroadcastReceiver receiver) { |
| if (sReceiversRegistered) return; |
| |
| IntentFilter intentFilter = new IntentFilter(); |
| intentFilter.addAction(ACTION_WORLD_CITIES_CHANGED); |
| intentFilter.addAction(ACTION_ON_DAY_CHANGE); |
| intentFilter.addAction(ACTION_CONFIGURATION_CHANGED); |
| context.getApplicationContext().registerReceiver(receiver, intentFilter, |
| Context.RECEIVER_NOT_EXPORTED); |
| |
| sReceiversRegistered = true; |
| } |
| |
| /** |
| * Called when the app widget changes sizes. |
| */ |
| @Override |
| public void onAppWidgetOptionsChanged(Context context, AppWidgetManager wm, int widgetId, |
| Bundle options) { |
| super.onAppWidgetOptionsChanged(context, wm, widgetId, options); |
| |
| // Scale the fonts of the clock to fit inside the new size |
| relayoutWidget(context, AppWidgetManager.getInstance(context), widgetId, options); |
| } |
| |
| /** |
| * Compute optimal font and icon sizes offscreen for both portrait and landscape orientations |
| * using the last known widget size and apply them to the widget. |
| */ |
| private static void relayoutWidget(Context context, AppWidgetManager wm, int widgetId, |
| Bundle options) { |
| final RemoteViews portrait = relayoutWidget(context, wm, widgetId, options, true); |
| 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); |
| } |
| |
| public static void updateAppWidget(Context context, AppWidgetManager wm, int widgetId) { |
| relayoutWidget(context, wm, widgetId, wm.getAppWidgetOptions(widgetId)); |
| } |
| |
| /** |
| * Compute optimal font and icon sizes offscreen for the given orientation. |
| */ |
| private static RemoteViews relayoutWidget(Context context, AppWidgetManager wm, int widgetId, |
| Bundle options, boolean portrait) { |
| // Create a remote view for the digital clock. |
| final String packageName = context.getPackageName(); |
| int[] layoutIds = WidgetUtils.getWidgetLayouts(context, widgetId); |
| 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.digital_widget)); |
| |
| // Tapping on the widget opens the app (if not on the lock screen). |
| if (Utils.isWidgetClickable(wm, widgetId)) { |
| final Intent openApp = new Intent(context, DeskClock.class); |
| final PendingIntent pi = PendingIntent.getActivity(context, 0, openApp, FLAG_IMMUTABLE); |
| rv.setOnClickPendingIntent(android.R.id.background, pi); |
| } |
| |
| // Configure child views of the remote view. |
| final CharSequence dateFormat = getDateFormat(context); |
| rv.setCharSequence(R.id.date, "setFormat12Hour", dateFormat); |
| rv.setCharSequence(R.id.date, "setFormat24Hour", dateFormat); |
| |
| final String nextAlarmTime = Utils.getNextAlarm(context); |
| if (TextUtils.isEmpty(nextAlarmTime)) { |
| rv.setViewVisibility(R.id.nextAlarm, GONE); |
| rv.setViewVisibility(R.id.nextAlarmIcon, GONE); |
| } else { |
| rv.setTextViewText(R.id.nextAlarm, nextAlarmTime); |
| rv.setViewVisibility(R.id.nextAlarm, VISIBLE); |
| rv.setViewVisibility(R.id.nextAlarmIcon, VISIBLE); |
| } |
| |
| if (options == null) { |
| options = wm.getAppWidgetOptions(widgetId); |
| } |
| |
| // Fetch the widget size selected by the user. |
| final Resources resources = context.getResources(); |
| final float density = resources.getDisplayMetrics().density; |
| final int minWidthPx = (int) (density * options.getInt(OPTION_APPWIDGET_MIN_WIDTH)); |
| final int minHeightPx = (int) (density * options.getInt(OPTION_APPWIDGET_MIN_HEIGHT)); |
| final int maxWidthPx = (int) (density * options.getInt(OPTION_APPWIDGET_MAX_WIDTH)); |
| 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 largestClockFontSizePx = |
| resources.getDimensionPixelSize(R.dimen.widget_max_clock_font_size); |
| |
| // Create a size template that describes the widget bounds. |
| final Sizes template = new Sizes(targetWidthPx, targetHeightPx, largestClockFontSizePx); |
| |
| // Compute optimal font sizes and icon sizes to fit within the widget bounds. |
| final Sizes sizes = optimizeSizes(context, template, nextAlarmTime); |
| if (LOGGER.isVerboseLoggable()) { |
| LOGGER.v(sizes.toString()); |
| } |
| |
| // Apply the computed sizes to the remote views. |
| rv.setInt(R.id.nextAlarmIcon, "setMaxHeight", sizes.mFontSizePx); |
| rv.setViewPadding(R.id.nextAlarmIcon, sizes.mIconPaddingPx, 0, sizes.mIconPaddingPx, 0); |
| 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); |
| // Shift the bottom view up by half of the non-removable TextView padding |
| rv.setViewLayoutMargin(R.id.bottom_view, RemoteViews.MARGIN_TOP, |
| sizes.mBottomViewMarginTopPx, COMPLEX_UNIT_PX); |
| rv.setViewPadding(R.id.bottom_view, 0, 0, 0, sizes.mBottomViewPaddingBottomPx); |
| |
| 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, |
| FLAG_IMMUTABLE); |
| rv.setPendingIntentTemplate(R.id.world_city_list, pi); |
| } |
| } |
| |
| return rv; |
| } |
| |
| /** |
| * Inflate an offscreen copy of the widget views. Binary search through the range of sizes until |
| * the optimal sizes that fit within the widget bounds are located. |
| */ |
| private static Sizes optimizeSizes(Context context, Sizes template, String nextAlarmTime) { |
| // Inflate a test layout to compute sizes at different font sizes. |
| final LayoutInflater inflater = LayoutInflater.from(context); |
| @SuppressLint("InflateParams") |
| final View sizer = inflater.inflate(R.layout.digital_widget_sizer, null /* root */); |
| |
| int padding = context.getResources().getDimensionPixelSize(R.dimen.widget_padding); |
| sizer.findViewById(R.id.widget_item).setPadding(padding, padding, padding, padding); |
| // Configure the date to display the current date string. |
| final CharSequence dateFormat = getDateFormat(context); |
| final TextClock date = sizer.findViewById(R.id.date); |
| date.setFormat12Hour(dateFormat); |
| date.setFormat24Hour(dateFormat); |
| |
| // Configure the next alarm views to display the next alarm time or be gone. |
| final TextView nextAlarmIcon = sizer.findViewById(R.id.nextAlarmIcon); |
| final TextView nextAlarm = sizer.findViewById(R.id.nextAlarm); |
| if (TextUtils.isEmpty(nextAlarmTime)) { |
| nextAlarm.setVisibility(GONE); |
| nextAlarmIcon.setVisibility(GONE); |
| } else { |
| nextAlarm.setText(nextAlarmTime); |
| nextAlarm.setVisibility(VISIBLE); |
| nextAlarmIcon.setVisibility(VISIBLE); |
| nextAlarmIcon.setTypeface(UiDataModel.getUiDataModel().getAlarmIconTypeface()); |
| } |
| |
| // Measure the widget at the largest possible size. |
| Sizes high = measure(template, template.getLargestClockFontSizePx(), sizer); |
| if (!high.hasViolations()) { |
| return high; |
| } |
| |
| // Measure the widget at the smallest possible size. |
| Sizes low = measure(template, template.getSmallestClockFontSizePx(), sizer); |
| if (low.hasViolations()) { |
| return low; |
| } |
| |
| // Binary search between the smallest and largest sizes until an optimum size is found. |
| while (low.getClockFontSizePx() != high.getClockFontSizePx()) { |
| final int midFontSize = (low.getClockFontSizePx() + high.getClockFontSizePx()) / 2; |
| if (midFontSize == low.getClockFontSizePx()) { |
| return low; |
| } |
| |
| final Sizes midSize = measure(template, midFontSize, sizer); |
| if (midSize.hasViolations()) { |
| high = midSize; |
| } else { |
| low = midSize; |
| } |
| } |
| |
| return low; |
| } |
| |
| /** |
| * 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 DataModel dm = DataModel.getDataModel(); |
| final List<City> selectedCities = dm.getSelectedCities(); |
| final boolean showHomeClock = dm.getShowHomeClock(); |
| if (selectedCities.isEmpty() && !showHomeClock) { |
| // 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() + 2); |
| zones.add(TimeZone.getDefault()); |
| if (showHomeClock) { |
| zones.add(dm.getHomeCity().getTimeZone()); |
| } |
| for (City city : selectedCities) { |
| zones.add(city.getTimeZone()); |
| } |
| final Date nextDay = Utils.getNextDay(new Date(), zones); |
| if (nextDay == null) { |
| return; |
| } |
| |
| // 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 | FLAG_IMMUTABLE); |
| 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 | FLAG_IMMUTABLE); |
| 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. |
| */ |
| private static Sizes measure(Sizes template, int clockFontSize, View sizer) { |
| // Create a copy of the given template sizes. |
| final Sizes measuredSizes = template.newSize(); |
| |
| // Configure the clock to display the widest time string. |
| final TextClock date = sizer.findViewById(R.id.date); |
| final TextClock clock = sizer.findViewById(R.id.clock); |
| final TextView nextAlarm = sizer.findViewById(R.id.nextAlarm); |
| final TextView nextAlarmIcon = sizer.findViewById(R.id.nextAlarmIcon); |
| final LinearLayout bottomView = sizer.findViewById(R.id.bottom_view); |
| |
| // Adjust the font sizes. |
| measuredSizes.setClockFontSizePx(clockFontSize); |
| clock.setText(getLongestTimeString(clock)); |
| clock.setTextSize(COMPLEX_UNIT_PX, measuredSizes.mClockFontSizePx); |
| date.setTextSize(COMPLEX_UNIT_PX, measuredSizes.mFontSizePx); |
| nextAlarm.setTextSize(COMPLEX_UNIT_PX, measuredSizes.mFontSizePx); |
| nextAlarmIcon.setTextSize(COMPLEX_UNIT_PX, measuredSizes.mIconFontSizePx); |
| nextAlarmIcon.setPadding(measuredSizes.mIconPaddingPx, 0, measuredSizes.mIconPaddingPx, 0); |
| // We want to shift the date line up by half the invisible padding of the clock (which is |
| // 33% of the font size) |
| measuredSizes.setBottomViewMarginPx(-1 * (int)(measuredSizes.mClockFontSizePx * 0.33 / 2)); |
| // We want the bottom padding of the date be equal to the top padding of the clock |
| measuredSizes.setBottomViewPaddingPx( |
| (int)(measuredSizes.mClockFontSizePx * 0.28 - measuredSizes.mFontSizePx * 0.33)); |
| bottomView.setPadding(0, 0, 0, measuredSizes.getBottomViewPaddingPx()); |
| LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) |
| bottomView.getLayoutParams(); |
| layoutParams.topMargin = measuredSizes.getBottomViewMarginTopPx(); |
| bottomView.setLayoutParams(layoutParams); |
| |
| // Measure and layout the sizer. |
| final int widthSize = View.MeasureSpec.getSize(measuredSizes.mTargetWidthPx); |
| final int heightSize = View.MeasureSpec.getSize(measuredSizes.mTargetHeightPx); |
| final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(widthSize, UNSPECIFIED); |
| final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(heightSize, UNSPECIFIED); |
| sizer.measure(widthMeasureSpec, heightMeasureSpec); |
| sizer.layout(0, 0, sizer.getMeasuredWidth(), sizer.getMeasuredHeight()); |
| |
| // Copy the measurements into the result object. |
| measuredSizes.mMeasuredWidthPx = sizer.getMeasuredWidth(); |
| measuredSizes.mMeasuredHeightPx = sizer.getMeasuredHeight(); |
| measuredSizes.mMeasuredTextClockWidthPx = clock.getMeasuredWidth(); |
| measuredSizes.mMeasuredTextClockHeightPx = clock.getMeasuredHeight(); |
| |
| return measuredSizes; |
| } |
| |
| /** |
| * @return "11:59" or "23:59" in the current locale |
| */ |
| private static CharSequence getLongestTimeString(TextClock clock) { |
| final CharSequence format = clock.is24HourModeEnabled() |
| ? clock.getFormat24Hour() |
| : clock.getFormat12Hour(); |
| final Calendar longestPMTime = Calendar.getInstance(); |
| longestPMTime.set(0, 0, 0, 23, 59); |
| return DateFormat.format(format, longestPMTime); |
| } |
| |
| /** |
| * @return the locale-specific date pattern |
| */ |
| private static String getDateFormat(Context context) { |
| final Locale locale = Locale.getDefault(); |
| final String skeleton = context.getString(R.string.abbrev_wday_month_day_no_year); |
| return DateFormat.getBestDateTimePattern(locale, skeleton); |
| } |
| |
| /** |
| * This class stores the target size of the widget as well as the measured size using a given |
| * clock font size. All other fonts and icons are scaled proportional to the clock font. |
| */ |
| private static final class Sizes { |
| |
| private final int mTargetWidthPx; |
| private final int mTargetHeightPx; |
| private final int mLargestClockFontSizePx; |
| private final int mSmallestClockFontSizePx; |
| |
| private int mMeasuredWidthPx; |
| private int mMeasuredHeightPx; |
| private int mMeasuredTextClockWidthPx; |
| private int mMeasuredTextClockHeightPx; |
| |
| /** The size of the font to use on the date / next alarm time fields. */ |
| private int mFontSizePx; |
| |
| /** The size of the font to use on the clock field. */ |
| private int mClockFontSizePx; |
| |
| private int mIconFontSizePx; |
| private int mIconPaddingPx; |
| |
| private int mBottomViewMarginTopPx; |
| private int mBottomViewPaddingBottomPx; |
| |
| private Sizes(int targetWidthPx, int targetHeightPx, int largestClockFontSizePx) { |
| mTargetWidthPx = targetWidthPx; |
| mTargetHeightPx = targetHeightPx; |
| mLargestClockFontSizePx = largestClockFontSizePx; |
| mSmallestClockFontSizePx = 1; |
| } |
| |
| private int getLargestClockFontSizePx() { return mLargestClockFontSizePx; } |
| private int getSmallestClockFontSizePx() { return mSmallestClockFontSizePx; } |
| private int getClockFontSizePx() { return mClockFontSizePx; } |
| private void setClockFontSizePx(int clockFontSizePx) { |
| mClockFontSizePx = clockFontSizePx; |
| mFontSizePx = max(1, round(clockFontSizePx / 5.5f)); |
| mIconFontSizePx = (int) (mFontSizePx * 1.4f); |
| mIconPaddingPx = mFontSizePx / 3; |
| } |
| |
| private int getBottomViewMarginTopPx() { return mBottomViewMarginTopPx; } |
| private void setBottomViewMarginPx(int bottomViewMarginPx) { |
| mBottomViewMarginTopPx = bottomViewMarginPx; |
| } |
| |
| private int getBottomViewPaddingPx() { return mBottomViewPaddingBottomPx; } |
| private void setBottomViewPaddingPx(int bottomViewPaddingPx) { |
| mBottomViewPaddingBottomPx = bottomViewPaddingPx; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| private Sizes newSize() { |
| return new Sizes(mTargetWidthPx, mTargetHeightPx, mLargestClockFontSizePx); |
| } |
| |
| @NonNull |
| @Override |
| public String toString() { |
| final StringBuilder builder = new StringBuilder(1000); |
| builder.append("\n"); |
| append(builder, "Target dimensions: %dpx x %dpx\n", mTargetWidthPx, mTargetHeightPx); |
| append(builder, "Last valid widget container measurement: %dpx x %dpx\n", |
| mMeasuredWidthPx, mMeasuredHeightPx); |
| append(builder, "Last text clock measurement: %dpx x %dpx\n", |
| mMeasuredTextClockWidthPx, mMeasuredTextClockHeightPx); |
| if (mMeasuredWidthPx > mTargetWidthPx) { |
| append(builder, "Measured width %dpx exceeded widget width %dpx\n", |
| mMeasuredWidthPx, mTargetWidthPx); |
| } |
| if (mMeasuredHeightPx > mTargetHeightPx) { |
| append(builder, "Measured height %dpx exceeded widget height %dpx\n", |
| mMeasuredHeightPx, mTargetHeightPx); |
| } |
| append(builder, "Clock font: %dpx\n", mClockFontSizePx); |
| return builder.toString(); |
| } |
| |
| private static void append(StringBuilder builder, String format, Object... args) { |
| builder.append(String.format(Locale.ENGLISH, format, args)); |
| } |
| } |
| } |