OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.physicalweb; | 5 package org.chromium.chrome.browser.physicalweb; |
6 | 6 |
7 import android.content.Context; | |
8 import android.content.SharedPreferences; | 7 import android.content.SharedPreferences; |
9 import android.os.AsyncTask; | 8 import android.os.AsyncTask; |
10 | 9 |
mattreynolds
2017/01/05 20:01:12
Remove newline (go/java-imports says to put all no
cco3
2017/01/05 20:20:10
Presubmit check says otherwise for chromium.
| |
10 import org.json.JSONArray; | |
11 import org.json.JSONException; | |
12 | |
11 import org.chromium.base.ContextUtils; | 13 import org.chromium.base.ContextUtils; |
12 import org.chromium.base.Log; | 14 import org.chromium.base.Log; |
13 import org.chromium.base.library_loader.LibraryLoader; | 15 import org.chromium.base.library_loader.LibraryLoader; |
14 import org.chromium.base.metrics.RecordHistogram; | 16 import org.chromium.base.metrics.RecordHistogram; |
15 import org.chromium.base.metrics.RecordUserAction; | 17 import org.chromium.base.metrics.RecordUserAction; |
16 import org.chromium.components.location.LocationUtils; | 18 import org.chromium.components.location.LocationUtils; |
17 import org.json.JSONArray; | |
18 import org.json.JSONException; | |
19 | 19 |
20 import java.util.concurrent.TimeUnit; | 20 import java.util.concurrent.TimeUnit; |
21 | 21 |
22 import javax.annotation.concurrent.ThreadSafe; | 22 import javax.annotation.concurrent.ThreadSafe; |
23 | 23 |
24 /** | 24 /** |
25 * Centralizes UMA data collection for the Physical Web feature. | 25 * Centralizes UMA data collection for the Physical Web feature. |
26 */ | 26 */ |
27 @ThreadSafe | 27 @ThreadSafe |
28 public class PhysicalWebUma { | 28 public class PhysicalWebUma { |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
62 private static final String DATA_CONNECTION = "DataConnection"; | 62 private static final String DATA_CONNECTION = "DataConnection"; |
63 private static final String LOCATION_PERMISSION = "LocationPermission"; | 63 private static final String LOCATION_PERMISSION = "LocationPermission"; |
64 private static final String LOCATION_SERVICES = "LocationServices"; | 64 private static final String LOCATION_SERVICES = "LocationServices"; |
65 private static final String PREFERENCE = "Preference"; | 65 private static final String PREFERENCE = "Preference"; |
66 private static final int BOOLEAN_BOUNDARY = 2; | 66 private static final int BOOLEAN_BOUNDARY = 2; |
67 private static final int TRISTATE_BOUNDARY = 3; | 67 private static final int TRISTATE_BOUNDARY = 3; |
68 | 68 |
69 /** | 69 /** |
70 * Records a URL selection. | 70 * Records a URL selection. |
71 */ | 71 */ |
72 public static void onUrlSelected(Context context) { | 72 public static void onUrlSelected() { |
73 handleAction(context, URL_SELECTED_COUNT); | 73 handleAction(URL_SELECTED_COUNT); |
74 } | 74 } |
75 | 75 |
76 /** | 76 /** |
77 * Records a tap on the opt-in decline button. | 77 * Records a tap on the opt-in decline button. |
78 */ | 78 */ |
79 public static void onOptInDeclineButtonPressed(Context context) { | 79 public static void onOptInDeclineButtonPressed() { |
80 handleAction(context, OPT_IN_DECLINE_BUTTON_PRESS_COUNT); | 80 handleAction(OPT_IN_DECLINE_BUTTON_PRESS_COUNT); |
81 } | 81 } |
82 | 82 |
83 /** | 83 /** |
84 * Records a tap on the opt-in enable button. | 84 * Records a tap on the opt-in enable button. |
85 */ | 85 */ |
86 public static void onOptInEnableButtonPressed(Context context) { | 86 public static void onOptInEnableButtonPressed() { |
87 handleAction(context, OPT_IN_ENABLE_BUTTON_PRESS_COUNT); | 87 handleAction(OPT_IN_ENABLE_BUTTON_PRESS_COUNT); |
88 } | 88 } |
89 | 89 |
90 /** | 90 /** |
91 * Records a display of a high priority opt-in notification. | 91 * Records a display of a high priority opt-in notification. |
92 */ | 92 */ |
93 public static void onOptInHighPriorityNotificationShown(Context context) { | 93 public static void onOptInHighPriorityNotificationShown() { |
94 handleAction(context, OPT_IN_HIGH_PRIORITY_NOTIFICATION_COUNT); | 94 handleAction(OPT_IN_HIGH_PRIORITY_NOTIFICATION_COUNT); |
95 } | 95 } |
96 | 96 |
97 /** | 97 /** |
98 * Records a display of a min priority opt-in notification. | 98 * Records a display of a min priority opt-in notification. |
99 */ | 99 */ |
100 public static void onOptInMinPriorityNotificationShown(Context context) { | 100 public static void onOptInMinPriorityNotificationShown() { |
101 handleAction(context, OPT_IN_MIN_PRIORITY_NOTIFICATION_COUNT); | 101 handleAction(OPT_IN_MIN_PRIORITY_NOTIFICATION_COUNT); |
102 } | 102 } |
103 | 103 |
104 /** | 104 /** |
105 * Records a display of the opt-in activity. | 105 * Records a display of the opt-in activity. |
106 */ | 106 */ |
107 public static void onOptInNotificationPressed(Context context) { | 107 public static void onOptInNotificationPressed() { |
108 handleAction(context, OPT_IN_NOTIFICATION_PRESS_COUNT); | 108 handleAction(OPT_IN_NOTIFICATION_PRESS_COUNT); |
109 } | 109 } |
110 | 110 |
111 /** | 111 /** |
112 * Records when the user disables the Physical Web fetaure. | 112 * Records when the user disables the Physical Web fetaure. |
113 */ | 113 */ |
114 public static void onPrefsFeatureDisabled(Context context) { | 114 public static void onPrefsFeatureDisabled() { |
115 handleAction(context, PREFS_FEATURE_DISABLED_COUNT); | 115 handleAction(PREFS_FEATURE_DISABLED_COUNT); |
116 } | 116 } |
117 | 117 |
118 /** | 118 /** |
119 * Records when the user enables the Physical Web fetaure. | 119 * Records when the user enables the Physical Web fetaure. |
120 */ | 120 */ |
121 public static void onPrefsFeatureEnabled(Context context) { | 121 public static void onPrefsFeatureEnabled() { |
122 handleAction(context, PREFS_FEATURE_ENABLED_COUNT); | 122 handleAction(PREFS_FEATURE_ENABLED_COUNT); |
123 } | 123 } |
124 | 124 |
125 /** | 125 /** |
126 * Records when the user denies the location permission when enabling the Ph ysical Web from the | 126 * Records when the user denies the location permission when enabling the Ph ysical Web from the |
127 * privacy settings menu. | 127 * privacy settings menu. |
128 */ | 128 */ |
129 public static void onPrefsLocationDenied(Context context) { | 129 public static void onPrefsLocationDenied() { |
130 handleAction(context, PREFS_LOCATION_DENIED_COUNT); | 130 handleAction(PREFS_LOCATION_DENIED_COUNT); |
131 } | 131 } |
132 | 132 |
133 /** | 133 /** |
134 * Records when the user grants the location permission when enabling the Ph ysical Web from the | 134 * Records when the user grants the location permission when enabling the Ph ysical Web from the |
135 * privacy settings menu. | 135 * privacy settings menu. |
136 */ | 136 */ |
137 public static void onPrefsLocationGranted(Context context) { | 137 public static void onPrefsLocationGranted() { |
138 handleAction(context, PREFS_LOCATION_GRANTED_COUNT); | 138 handleAction(PREFS_LOCATION_GRANTED_COUNT); |
139 } | 139 } |
140 | 140 |
141 /** | 141 /** |
142 * Records a response time from PWS for a resolution during a background sca n. | 142 * Records a response time from PWS for a resolution during a background sca n. |
143 * @param duration The length of time PWS took to respond. | 143 * @param duration The length of time PWS took to respond. |
144 */ | 144 */ |
145 public static void onBackgroundPwsResolution(Context context, long duration) { | 145 public static void onBackgroundPwsResolution(long duration) { |
146 handleTime(context, PWS_BACKGROUND_RESOLVE_TIMES, duration, TimeUnit.MIL LISECONDS); | 146 handleTime(PWS_BACKGROUND_RESOLVE_TIMES, duration, TimeUnit.MILLISECONDS ); |
147 } | 147 } |
148 | 148 |
149 /** | 149 /** |
150 * Records a response time from PWS for a resolution during a foreground sca n that is not | 150 * Records a response time from PWS for a resolution during a foreground sca n that is not |
151 * explicitly user-initiated through a refresh. | 151 * explicitly user-initiated through a refresh. |
152 * @param duration The length of time PWS took to respond. | 152 * @param duration The length of time PWS took to respond. |
153 */ | 153 */ |
154 public static void onForegroundPwsResolution(Context context, long duration) { | 154 public static void onForegroundPwsResolution(long duration) { |
155 handleTime(context, PWS_FOREGROUND_RESOLVE_TIMES, duration, TimeUnit.MIL LISECONDS); | 155 handleTime(PWS_FOREGROUND_RESOLVE_TIMES, duration, TimeUnit.MILLISECONDS ); |
156 } | 156 } |
157 | 157 |
158 /** | 158 /** |
159 * Records a response time from PWS for a resolution during a foreground sca n that is explicitly | 159 * Records a response time from PWS for a resolution during a foreground sca n that is explicitly |
160 * user-initiated through a refresh. | 160 * user-initiated through a refresh. |
161 * @param duration The length of time PWS took to respond. | 161 * @param duration The length of time PWS took to respond. |
162 */ | 162 */ |
163 public static void onRefreshPwsResolution(Context context, long duration) { | 163 public static void onRefreshPwsResolution(long duration) { |
164 handleTime(context, PWS_REFRESH_RESOLVE_TIMES, duration, TimeUnit.MILLIS ECONDS); | 164 handleTime(PWS_REFRESH_RESOLVE_TIMES, duration, TimeUnit.MILLISECONDS); |
165 } | 165 } |
166 | 166 |
167 /** | 167 /** |
168 * Records number of URLs displayed to a user when the URL list is first dis played. | 168 * Records number of URLs displayed to a user when the URL list is first dis played. |
169 * @param numUrls The number of URLs displayed to a user. | 169 * @param numUrls The number of URLs displayed to a user. |
170 */ | 170 */ |
171 public static void onUrlsDisplayed(Context context, int numUrls) { | 171 public static void onUrlsDisplayed(int numUrls) { |
172 if (LibraryLoader.isInitialized()) { | 172 if (LibraryLoader.isInitialized()) { |
173 RecordHistogram.recordCountHistogram(TOTAL_URLS_INITIAL_COUNTS, numU rls); | 173 RecordHistogram.recordCountHistogram(TOTAL_URLS_INITIAL_COUNTS, numU rls); |
174 } else { | 174 } else { |
175 storeValue(context, TOTAL_URLS_INITIAL_COUNTS, numUrls); | 175 storeValue(TOTAL_URLS_INITIAL_COUNTS, numUrls); |
176 } | 176 } |
177 } | 177 } |
178 | 178 |
179 /** | 179 /** |
180 * Records number of URLs displayed to a user when the user refreshes the UR L list. | 180 * Records number of URLs displayed to a user when the user refreshes the UR L list. |
181 * @param numUrls The number of URLs displayed to a user. | 181 * @param numUrls The number of URLs displayed to a user. |
182 */ | 182 */ |
183 public static void onUrlsRefreshed(Context context, int numUrls) { | 183 public static void onUrlsRefreshed(int numUrls) { |
184 if (LibraryLoader.isInitialized()) { | 184 if (LibraryLoader.isInitialized()) { |
185 RecordHistogram.recordCountHistogram(TOTAL_URLS_REFRESH_COUNTS, numU rls); | 185 RecordHistogram.recordCountHistogram(TOTAL_URLS_REFRESH_COUNTS, numU rls); |
186 } else { | 186 } else { |
187 storeValue(context, TOTAL_URLS_REFRESH_COUNTS, numUrls); | 187 storeValue(TOTAL_URLS_REFRESH_COUNTS, numUrls); |
188 } | 188 } |
189 } | 189 } |
190 | 190 |
191 /** | 191 /** |
192 * Records a ListUrlActivity referral. | 192 * Records a ListUrlActivity referral. |
193 * @param refer The type of referral. This enum is listed as PhysicalWebAct ivityReferer in | 193 * @param refer The type of referral. This enum is listed as PhysicalWebAct ivityReferer in |
194 * histograms.xml. | 194 * histograms.xml. |
195 */ | 195 */ |
196 public static void onActivityReferral(Context context, int referer) { | 196 public static void onActivityReferral(int referer) { |
197 handleEnum(context, ACTIVITY_REFERRALS, referer, ListUrlsActivity.REFERE R_BOUNDARY); | 197 handleEnum(ACTIVITY_REFERRALS, referer, ListUrlsActivity.REFERER_BOUNDAR Y); |
198 switch (referer) { | 198 switch (referer) { |
199 case ListUrlsActivity.NOTIFICATION_REFERER: | 199 case ListUrlsActivity.NOTIFICATION_REFERER: |
200 handleTime(context, STANDARD_NOTIFICATION_PRESS_DELAYS, | 200 handleTime(STANDARD_NOTIFICATION_PRESS_DELAYS, |
201 UrlManager.getInstance().getTimeSinceNotificationUpdate( ), | 201 UrlManager.getInstance().getTimeSinceNotificationUpdate( ), |
202 TimeUnit.MILLISECONDS); | 202 TimeUnit.MILLISECONDS); |
203 break; | 203 break; |
204 case ListUrlsActivity.OPTIN_REFERER: | 204 case ListUrlsActivity.OPTIN_REFERER: |
205 handleTime(context, OPT_IN_NOTIFICATION_PRESS_DELAYS, | 205 handleTime(OPT_IN_NOTIFICATION_PRESS_DELAYS, |
206 UrlManager.getInstance().getTimeSinceNotificationUpdate( ), | 206 UrlManager.getInstance().getTimeSinceNotificationUpdate( ), |
207 TimeUnit.MILLISECONDS); | 207 TimeUnit.MILLISECONDS); |
208 break; | 208 break; |
209 case ListUrlsActivity.PREFERENCE_REFERER: | 209 case ListUrlsActivity.PREFERENCE_REFERER: |
210 recordPhysicalWebState(context, LAUNCH_FROM_PREFERENCES); | 210 recordPhysicalWebState(LAUNCH_FROM_PREFERENCES); |
211 break; | 211 break; |
212 case ListUrlsActivity.DIAGNOSTICS_REFERER: | 212 case ListUrlsActivity.DIAGNOSTICS_REFERER: |
213 recordPhysicalWebState(context, LAUNCH_FROM_DIAGNOSTICS); | 213 recordPhysicalWebState(LAUNCH_FROM_DIAGNOSTICS); |
214 break; | 214 break; |
215 default: | 215 default: |
216 break; | 216 break; |
217 } | 217 } |
218 } | 218 } |
219 | 219 |
220 /** | 220 /** |
221 * Calculate a Physical Web state. | 221 * Calculate a Physical Web state. |
222 * The Physical Web state includes: | 222 * The Physical Web state includes: |
223 * - The location provider | 223 * - The location provider |
224 * - The location permission | 224 * - The location permission |
225 * - The bluetooth status | 225 * - The bluetooth status |
226 * - The data connection status | 226 * - The data connection status |
227 * - The Physical Web preference status | 227 * - The Physical Web preference status |
228 */ | 228 */ |
229 public static void recordPhysicalWebState(Context context, String actionName ) { | 229 public static void recordPhysicalWebState(String actionName) { |
230 LocationUtils locationUtils = LocationUtils.getInstance(); | 230 LocationUtils locationUtils = LocationUtils.getInstance(); |
231 handleEnum(context, createStateString(LOCATION_SERVICES, actionName), | 231 handleEnum(createStateString(LOCATION_SERVICES, actionName), |
232 locationUtils.isSystemLocationSettingEnabled() ? 1 : 0, BOOLEAN_ BOUNDARY); | 232 locationUtils.isSystemLocationSettingEnabled() ? 1 : 0, BOOLEAN_ BOUNDARY); |
233 handleEnum(context, createStateString(LOCATION_PERMISSION, actionName), | 233 handleEnum(createStateString(LOCATION_PERMISSION, actionName), |
234 locationUtils.hasAndroidLocationPermission() ? 1 : 0, BOOLEAN_BO UNDARY); | 234 locationUtils.hasAndroidLocationPermission() ? 1 : 0, BOOLEAN_BO UNDARY); |
235 handleEnum(context, createStateString(BLUETOOTH, actionName), | 235 handleEnum(createStateString(BLUETOOTH, actionName), |
236 Utils.getBluetoothEnabledStatus(), TRISTATE_BOUNDARY); | 236 Utils.getBluetoothEnabledStatus(), TRISTATE_BOUNDARY); |
237 handleEnum(context, createStateString(DATA_CONNECTION, actionName), | 237 handleEnum(createStateString(DATA_CONNECTION, actionName), |
238 Utils.isDataConnectionActive() ? 1 : 0, BOOLEAN_BOUNDARY); | 238 Utils.isDataConnectionActive() ? 1 : 0, BOOLEAN_BOUNDARY); |
239 int preferenceState = 2; | 239 int preferenceState = 2; |
240 if (!PhysicalWeb.isOnboarding()) { | 240 if (!PhysicalWeb.isOnboarding()) { |
241 preferenceState = PhysicalWeb.isPhysicalWebPreferenceEnabled() ? 1 : 0; | 241 preferenceState = PhysicalWeb.isPhysicalWebPreferenceEnabled() ? 1 : 0; |
242 } | 242 } |
243 handleEnum(context, createStateString(PREFERENCE, actionName), | 243 handleEnum(createStateString(PREFERENCE, actionName), |
244 preferenceState, TRISTATE_BOUNDARY); | 244 preferenceState, TRISTATE_BOUNDARY); |
245 } | 245 } |
246 | 246 |
247 /** | 247 /** |
248 * Uploads metrics that we have deferred for uploading. | 248 * Uploads metrics that we have deferred for uploading. |
249 */ | 249 */ |
250 public static void uploadDeferredMetrics() { | 250 public static void uploadDeferredMetrics() { |
251 // Read the metrics. | 251 // Read the metrics. |
252 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); | 252 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
253 if (prefs.getBoolean(HAS_DEFERRED_METRICS_KEY, false)) { | 253 if (prefs.getBoolean(HAS_DEFERRED_METRICS_KEY, false)) { |
254 AsyncTask.THREAD_POOL_EXECUTOR.execute(new UmaUploader(prefs)); | 254 AsyncTask.THREAD_POOL_EXECUTOR.execute(new UmaUploader(prefs)); |
255 } | 255 } |
256 } | 256 } |
257 | 257 |
258 private static String createStateString(String stateName, String actionName) { | 258 private static String createStateString(String stateName, String actionName) { |
259 return PHYSICAL_WEB_STATE + "." + stateName + "." + actionName; | 259 return PHYSICAL_WEB_STATE + "." + stateName + "." + actionName; |
260 } | 260 } |
261 | 261 |
262 private static void storeAction(Context context, String key) { | 262 private static void storeAction(String key) { |
263 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); | 263 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
264 int count = prefs.getInt(key, 0); | 264 int count = prefs.getInt(key, 0); |
265 prefs.edit() | 265 prefs.edit() |
266 .putBoolean(HAS_DEFERRED_METRICS_KEY, true) | 266 .putBoolean(HAS_DEFERRED_METRICS_KEY, true) |
267 .putInt(key, count + 1) | 267 .putInt(key, count + 1) |
268 .apply(); | 268 .apply(); |
269 } | 269 } |
270 | 270 |
271 private static void storeValue(Context context, String key, Object value) { | 271 private static void storeValue(String key, Object value) { |
272 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); | 272 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
273 SharedPreferences.Editor prefsEditor = prefs.edit(); | 273 SharedPreferences.Editor prefsEditor = prefs.edit(); |
274 JSONArray values = null; | 274 JSONArray values = null; |
275 try { | 275 try { |
276 values = new JSONArray(prefs.getString(key, "[]")); | 276 values = new JSONArray(prefs.getString(key, "[]")); |
277 values.put(value); | 277 values.put(value); |
278 prefsEditor | 278 prefsEditor |
279 .putBoolean(HAS_DEFERRED_METRICS_KEY, true) | 279 .putBoolean(HAS_DEFERRED_METRICS_KEY, true) |
280 .putString(key, values.toString()) | 280 .putString(key, values.toString()) |
281 .apply(); | 281 .apply(); |
282 } catch (JSONException e) { | 282 } catch (JSONException e) { |
283 Log.e(TAG, "JSONException when storing " + key + " stats", e); | 283 Log.e(TAG, "JSONException when storing " + key + " stats", e); |
284 prefsEditor.remove(key).apply(); | 284 prefsEditor.remove(key).apply(); |
285 return; | 285 return; |
286 } | 286 } |
287 prefsEditor.putString(key, values.toString()).apply(); | 287 prefsEditor.putString(key, values.toString()).apply(); |
288 } | 288 } |
289 | 289 |
290 private static void handleAction(Context context, String key) { | 290 private static void handleAction(String key) { |
291 if (LibraryLoader.isInitialized()) { | 291 if (LibraryLoader.isInitialized()) { |
292 RecordUserAction.record(key); | 292 RecordUserAction.record(key); |
293 } else { | 293 } else { |
294 storeAction(context, key); | 294 storeAction(key); |
295 } | 295 } |
296 } | 296 } |
297 | 297 |
298 private static void handleTime(Context context, String key, long duration, T imeUnit tu) { | 298 private static void handleTime(String key, long duration, TimeUnit tu) { |
299 if (LibraryLoader.isInitialized()) { | 299 if (LibraryLoader.isInitialized()) { |
300 RecordHistogram.recordTimesHistogram(key, duration, tu); | 300 RecordHistogram.recordTimesHistogram(key, duration, tu); |
301 } else { | 301 } else { |
302 storeValue(context, key, duration); | 302 storeValue(key, duration); |
303 } | 303 } |
304 } | 304 } |
305 | 305 |
306 private static void handleEnum(Context context, String key, int value, int b oundary) { | 306 private static void handleEnum(String key, int value, int boundary) { |
307 if (LibraryLoader.isInitialized()) { | 307 if (LibraryLoader.isInitialized()) { |
308 RecordHistogram.recordEnumeratedHistogram(key, value, boundary); | 308 RecordHistogram.recordEnumeratedHistogram(key, value, boundary); |
309 } else { | 309 } else { |
310 storeValue(context, key, value); | 310 storeValue(key, value); |
311 } | 311 } |
312 } | 312 } |
313 | 313 |
314 private static class UmaUploader implements Runnable { | 314 private static class UmaUploader implements Runnable { |
315 SharedPreferences mPrefs; | 315 SharedPreferences mPrefs; |
316 | 316 |
317 UmaUploader(SharedPreferences prefs) { | 317 UmaUploader(SharedPreferences prefs) { |
318 mPrefs = prefs; | 318 mPrefs = prefs; |
319 } | 319 } |
320 | 320 |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
445 if (values == null) { | 445 if (values == null) { |
446 Log.e(TAG, "Error reporting " + key + " with values: " + jsonEnu msStr); | 446 Log.e(TAG, "Error reporting " + key + " with values: " + jsonEnu msStr); |
447 return; | 447 return; |
448 } | 448 } |
449 for (Integer value: values) { | 449 for (Integer value: values) { |
450 RecordHistogram.recordEnumeratedHistogram(key, value, boundary); | 450 RecordHistogram.recordEnumeratedHistogram(key, value, boundary); |
451 } | 451 } |
452 } | 452 } |
453 } | 453 } |
454 } | 454 } |
OLD | NEW |