• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
FormattedStringModel(Context context)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      */
getFormattedNumber(int value)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      */
getFormattedNumber(int value, int length)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      */
getFormattedNumber(boolean negative, int value, int length)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      */
getShortWeekday(int calendarDay)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      */
getLongWeekday(int calendarDay)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
onReceive(Context context, Intent intent)191         public void onReceive(Context context, Intent intent) {
192             mNumberFormatCache.clear();
193             mShortWeekdayNames = null;
194             mLongWeekdayNames = null;
195         }
196     }
197 }