Home | History | Annotate | Download | only in uidata
      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.deskclock.uidata;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.util.ArrayMap;
     24 import android.util.SparseArray;
     25 
     26 import java.text.SimpleDateFormat;
     27 import java.util.Calendar;
     28 import java.util.GregorianCalendar;
     29 import java.util.Locale;
     30 import java.util.Map;
     31 
     32 import static java.util.Calendar.JULY;
     33 
     34 /**
     35  * All formatted strings that are cached for performance are accessed via this model.
     36  */
     37 final class FormattedStringModel {
     38 
     39     /** Clears data structures containing data that is locale-sensitive. */
     40     @SuppressWarnings("FieldCanBeLocal")
     41     private final BroadcastReceiver mLocaleChangedReceiver = new LocaleChangedReceiver();
     42 
     43     /**
     44      * Caches formatted numbers in the current locale padded with zeroes to requested lengths.
     45      * The first level of the cache maps length to the second level of the cache.
     46      * The second level of the cache maps an integer to a formatted String in the current locale.
     47      */
     48     private final SparseArray<SparseArray<String>> mNumberFormatCache = new SparseArray<>(3);
     49 
     50     /** Single-character version of weekday names; e.g.: 'S', 'M', 'T', 'W', 'T', 'F', 'S' */
     51     private Map<Integer, String> mShortWeekdayNames;
     52 
     53     /** Full weekday names; e.g.: 'Sunday', 'Monday', 'Tuesday', etc. */
     54     private Map<Integer, String> mLongWeekdayNames;
     55 
     56     FormattedStringModel(Context context) {
     57         // Clear caches affected by locale when locale changes.
     58         final IntentFilter localeBroadcastFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
     59         context.registerReceiver(mLocaleChangedReceiver, localeBroadcastFilter);
     60     }
     61 
     62     /**
     63      * This method is intended to be used when formatting numbers occurs in a hotspot such as the
     64      * update loop of a timer or stopwatch. It returns cached results when possible in order to
     65      * provide speed and limit garbage to be collected by the virtual machine.
     66      *
     67      * @param value a positive integer to format as a String
     68      * @return the {@code value} formatted as a String in the current locale
     69      * @throws IllegalArgumentException if {@code value} is negative
     70      */
     71     String getFormattedNumber(int value) {
     72         final int length = value == 0 ? 1 : ((int) Math.log10(value) + 1);
     73         return getFormattedNumber(false, value, length);
     74     }
     75 
     76     /**
     77      * This method is intended to be used when formatting numbers occurs in a hotspot such as the
     78      * update loop of a timer or stopwatch. It returns cached results when possible in order to
     79      * provide speed and limit garbage to be collected by the virtual machine.
     80      *
     81      * @param value a positive integer to format as a String
     82      * @param length the length of the String; zeroes are padded to match this length
     83      * @return the {@code value} formatted as a String in the current locale and padded to the
     84      *      requested {@code length}
     85      * @throws IllegalArgumentException if {@code value} is negative
     86      */
     87     String getFormattedNumber(int value, int length) {
     88         return getFormattedNumber(false, value, length);
     89     }
     90 
     91     /**
     92      * This method is intended to be used when formatting numbers occurs in a hotspot such as the
     93      * update loop of a timer or stopwatch. It returns cached results when possible in order to
     94      * provide speed and limit garbage to be collected by the virtual machine.
     95      *
     96      * @param negative force a minus sign (-) onto the display, even if {@code value} is {@code 0}
     97      * @param value a positive integer to format as a String
     98      * @param length the length of the String; zeroes are padded to match this length. If
     99      *      {@code negative} is {@code true} the return value will contain a minus sign and a total
    100      *      length of {@code length + 1}.
    101      * @return the {@code value} formatted as a String in the current locale and padded to the
    102      *      requested {@code length}
    103      * @throws IllegalArgumentException if {@code value} is negative
    104      */
    105     String getFormattedNumber(boolean negative, int value, int length) {
    106         if (value < 0) {
    107             throw new IllegalArgumentException("value may not be negative: " + value);
    108         }
    109 
    110         // Look up the value cache using the length; -ve and +ve values are cached separately.
    111         final int lengthCacheKey = negative ? -length : length;
    112         SparseArray<String> valueCache = mNumberFormatCache.get(lengthCacheKey);
    113         if (valueCache == null) {
    114             valueCache = new SparseArray<>((int) Math.pow(10, length));
    115             mNumberFormatCache.put(lengthCacheKey, valueCache);
    116         }
    117 
    118         // Look up the cached formatted value using the value.
    119         String formatted = valueCache.get(value);
    120         if (formatted == null) {
    121             final String sign = negative ? "" : "";
    122             formatted = String.format(Locale.getDefault(), sign + "%0" + length + "d", value);
    123             valueCache.put(value, formatted);
    124         }
    125 
    126         return formatted;
    127     }
    128 
    129     /**
    130      * @param calendarDay any of the following values
    131      *                     <ul>
    132      *                     <li>{@link Calendar#SUNDAY}</li>
    133      *                     <li>{@link Calendar#MONDAY}</li>
    134      *                     <li>{@link Calendar#TUESDAY}</li>
    135      *                     <li>{@link Calendar#WEDNESDAY}</li>
    136      *                     <li>{@link Calendar#THURSDAY}</li>
    137      *                     <li>{@link Calendar#FRIDAY}</li>
    138      *                     <li>{@link Calendar#SATURDAY}</li>
    139      *                     </ul>
    140      * @return single-character weekday name; e.g.: 'S', 'M', 'T', 'W', 'T', 'F', 'S'
    141      */
    142     String getShortWeekday(int calendarDay) {
    143         if (mShortWeekdayNames == null) {
    144             mShortWeekdayNames = new ArrayMap<>(7);
    145 
    146             final SimpleDateFormat format = new SimpleDateFormat("ccccc", Locale.getDefault());
    147             for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
    148                 final Calendar calendar = new GregorianCalendar(2014, JULY, 20 + i - 1);
    149                 final String weekday = format.format(calendar.getTime());
    150                 mShortWeekdayNames.put(i, weekday);
    151             }
    152         }
    153 
    154         return mShortWeekdayNames.get(calendarDay);
    155     }
    156 
    157     /**
    158      * @param calendarDay any of the following values
    159      *                     <ul>
    160      *                     <li>{@link Calendar#SUNDAY}</li>
    161      *                     <li>{@link Calendar#MONDAY}</li>
    162      *                     <li>{@link Calendar#TUESDAY}</li>
    163      *                     <li>{@link Calendar#WEDNESDAY}</li>
    164      *                     <li>{@link Calendar#THURSDAY}</li>
    165      *                     <li>{@link Calendar#FRIDAY}</li>
    166      *                     <li>{@link Calendar#SATURDAY}</li>
    167      *                     </ul>
    168      * @return full weekday name; e.g.: 'Sunday', 'Monday', 'Tuesday', etc.
    169      */
    170     String getLongWeekday(int calendarDay) {
    171         if (mLongWeekdayNames == null) {
    172             mLongWeekdayNames = new ArrayMap<>(7);
    173 
    174             final Calendar calendar = new GregorianCalendar(2014, JULY, 20);
    175             final SimpleDateFormat format = new SimpleDateFormat("EEEE", Locale.getDefault());
    176             for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
    177                 final String weekday = format.format(calendar.getTime());
    178                 mLongWeekdayNames.put(i, weekday);
    179                 calendar.add(Calendar.DAY_OF_YEAR, 1);
    180             }
    181         }
    182 
    183         return mLongWeekdayNames.get(calendarDay);
    184     }
    185 
    186     /**
    187      * Cached information that is locale-sensitive must be cleared in response to locale changes.
    188      */
    189     private final class LocaleChangedReceiver extends BroadcastReceiver {
    190         @Override
    191         public void onReceive(Context context, Intent intent) {
    192             mNumberFormatCache.clear();
    193             mShortWeekdayNames = null;
    194             mLongWeekdayNames = null;
    195         }
    196     }
    197 }