• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.internal.telephony.satellite.metrics;
18 
19 import android.annotation.NonNull;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.os.BatteryManager;
25 import android.os.SystemClock;
26 import android.telephony.satellite.SatelliteManager;
27 import android.util.Log;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.internal.telephony.metrics.SatelliteStats;
31 import com.android.internal.telephony.satellite.SatelliteServiceUtils;
32 
33 /**
34  * Stats to log to satellite metrics
35  */
36 public class ControllerMetricsStats {
37     private static final int ADD_COUNT = 1;
38     private static final String TAG = ControllerMetricsStats.class.getSimpleName();
39 
40     private static ControllerMetricsStats sInstance;
41 
42     private final Context mContext;
43     private SatelliteStats mSatelliteStats;
44 
45     private long mSatelliteOnTimeMillis;
46     private int mBatteryLevelWhenServiceOn;
47     private boolean mIsSatelliteModemOn;
48     private Boolean mIsBatteryCharged = null;
49     private long mBatteryChargedStartTime;
50     private int mTotalBatteryChargeTimeSec;
51 
52     /**
53      * @return The singleton instance of ControllerMetricsStats.
54      */
getInstance()55     public static ControllerMetricsStats getInstance() {
56         if (sInstance == null) {
57             loge("ControllerMetricsStats was not yet initialized.");
58         }
59         return sInstance;
60     }
61 
62     /**
63      * Create the ControllerMetricsStats singleton instance.
64      *
65      * @param context   The Context for the ControllerMetricsStats.
66      * @return          The singleton instance of ControllerMetricsStats.
67      */
make(@onNull Context context)68     public static ControllerMetricsStats make(@NonNull Context context) {
69         if (sInstance == null) {
70             sInstance = new ControllerMetricsStats(context);
71         }
72         return sInstance;
73     }
74 
75     /**
76      * Create the ControllerMetricsStats singleton instance, testing purpose only.
77      *
78      * @param context   The Context for the ControllerMetricsStats.
79      * @param satelliteStats SatelliteStats instance to test
80      * @return          The singleton instance of ControllerMetricsStats.
81      */
82     @VisibleForTesting
make(@onNull Context context, @NonNull SatelliteStats satelliteStats)83     public static ControllerMetricsStats make(@NonNull Context context,
84             @NonNull SatelliteStats satelliteStats) {
85         if (sInstance == null) {
86             sInstance = new ControllerMetricsStats(context, satelliteStats);
87         }
88         return sInstance;
89     }
90 
91     /**
92      * Create the ControllerMetricsStats to manage metrics report for
93      * {@link SatelliteStats.SatelliteControllerParams}
94      * @param context The Context for the ControllerMetricsStats.
95      */
ControllerMetricsStats(@onNull Context context)96     ControllerMetricsStats(@NonNull Context context) {
97         mContext = context;
98         mSatelliteStats = SatelliteStats.getInstance();
99     }
100 
101     /**
102      * Create the ControllerMetricsStats to manage metrics report for
103      * {@link SatelliteStats.SatelliteControllerParams}
104      *
105      * @param context           The Context for the ControllerMetricsStats.
106      * @param satelliteStats    SatelliteStats object used for testing purpose
107      */
108     @VisibleForTesting
ControllerMetricsStats(@onNull Context context, @NonNull SatelliteStats satelliteStats)109     protected ControllerMetricsStats(@NonNull Context context,
110             @NonNull SatelliteStats satelliteStats) {
111         mContext = context;
112         mSatelliteStats = satelliteStats;
113     }
114 
115     /** Report a counter when an attempt for satellite service on is successfully done */
reportServiceEnablementSuccessCount()116     public void reportServiceEnablementSuccessCount() {
117         logd("reportServiceEnablementSuccessCount()");
118         mSatelliteStats.onSatelliteControllerMetrics(
119                 new SatelliteStats.SatelliteControllerParams.Builder()
120                         .setCountOfSatelliteServiceEnablementsSuccess(ADD_COUNT)
121                         .build());
122     }
123 
124     /** Report a counter when an attempt for satellite service on is failed */
reportServiceEnablementFailCount()125     public void reportServiceEnablementFailCount() {
126         logd("reportServiceEnablementFailCount()");
127         mSatelliteStats.onSatelliteControllerMetrics(
128                 new SatelliteStats.SatelliteControllerParams.Builder()
129                         .setCountOfSatelliteServiceEnablementsFail(ADD_COUNT)
130                         .build());
131     }
132 
133     /** Report a counter when an attempt for outgoing datagram is successfully done */
reportOutgoingDatagramSuccessCount( @onNull @atelliteManager.DatagramType int datagramType, boolean isDemoMode)134     public void reportOutgoingDatagramSuccessCount(
135             @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) {
136         SatelliteStats.SatelliteControllerParams.Builder builder =
137                 new SatelliteStats.SatelliteControllerParams.Builder();
138 
139         if (isDemoMode) {
140             builder.setCountOfDemoModeOutgoingDatagramSuccess(ADD_COUNT);
141         } else {
142             builder.setCountOfOutgoingDatagramSuccess(ADD_COUNT);
143             if (SatelliteServiceUtils.isSosMessage(datagramType)) {
144                 builder.setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT);
145             } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
146                 builder.setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT);
147             } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
148                 builder.setCountOfDatagramTypeKeepAliveSuccess(ADD_COUNT).build();
149             } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_SMS) {
150                 builder.setCountOfOutgoingDatagramTypeSmsSuccess(ADD_COUNT);
151             }
152         }
153 
154         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
155         logd("reportServiceEnablementSuccessCount(): " + controllerParam);
156         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
157     }
158 
159     /** Report a counter when an attempt for outgoing datagram is failed */
reportOutgoingDatagramFailCount( @onNull @atelliteManager.DatagramType int datagramType, boolean isDemoMode)160     public void reportOutgoingDatagramFailCount(
161             @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) {
162         SatelliteStats.SatelliteControllerParams.Builder builder =
163                 new SatelliteStats.SatelliteControllerParams.Builder();
164 
165         if (isDemoMode) {
166             builder.setCountOfDemoModeOutgoingDatagramFail(ADD_COUNT);
167         } else {
168             builder.setCountOfOutgoingDatagramFail(ADD_COUNT);
169             if (SatelliteServiceUtils.isSosMessage(datagramType)) {
170                 builder.setCountOfDatagramTypeSosSmsFail(ADD_COUNT);
171             } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
172                 builder.setCountOfDatagramTypeLocationSharingFail(ADD_COUNT);
173             } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
174                 builder.setCountOfDatagramTypeKeepAliveFail(ADD_COUNT);
175             } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_SMS) {
176                 builder.setCountOfOutgoingDatagramTypeSmsFail(ADD_COUNT);
177             }
178         }
179 
180         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
181         logd("reportOutgoingDatagramFailCount(): " + controllerParam);
182         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
183     }
184 
185     /** Increase counters for successful and failed incoming datagram attempts */
reportIncomingDatagramCount( @onNull @atelliteManager.SatelliteResult int result, boolean isDemoMode)186     public void reportIncomingDatagramCount(
187             @NonNull @SatelliteManager.SatelliteResult int result, boolean isDemoMode) {
188         SatelliteStats.SatelliteControllerParams.Builder builder =
189                 new SatelliteStats.SatelliteControllerParams.Builder();
190         if (isDemoMode) {
191             if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
192                 builder.setCountOfDemoModeIncomingDatagramSuccess(ADD_COUNT);
193             } else {
194                 builder.setCountOfDemoModeIncomingDatagramFail(ADD_COUNT);
195             }
196         } else {
197             if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
198                 builder.setCountOfIncomingDatagramSuccess(ADD_COUNT)
199                         .setCountOfIncomingDatagramTypeSosSmsSuccess(ADD_COUNT);
200 
201             } else {
202                 builder.setCountOfIncomingDatagramFail(ADD_COUNT)
203                         .setCountOfIncomingDatagramTypeSosSmsFail(ADD_COUNT);
204             }
205         }
206         SatelliteStats.SatelliteControllerParams  controllerParam = builder.build();
207         logd("reportIncomingDatagramCount(): " + controllerParam);
208         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
209     }
210 
211     /** Increase counters for successful and failed incoming ntn sms attempts */
reportIncomingNtnSmsCount( @onNull @atelliteManager.SatelliteResult int result)212     public void reportIncomingNtnSmsCount(
213             @NonNull @SatelliteManager.SatelliteResult int result) {
214         SatelliteStats.SatelliteControllerParams.Builder builder =
215                 new SatelliteStats.SatelliteControllerParams.Builder();
216         if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
217             builder.setCountOfIncomingDatagramTypeSmsSuccess(ADD_COUNT);
218         } else {
219             builder.setCountOfIncomingDatagramTypeSmsFail(ADD_COUNT);
220         }
221         SatelliteStats.SatelliteControllerParams  controllerParam = builder.build();
222         logd("reportIncomingNtnSmsCount(): " + controllerParam);
223         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
224     }
225 
226     /** Report a counter when an attempt for de-provision is success or not */
reportProvisionCount(@onNull @atelliteManager.SatelliteResult int result)227     public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteResult int result) {
228         SatelliteStats.SatelliteControllerParams controllerParam;
229         if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
230             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
231                     .setCountOfProvisionSuccess(ADD_COUNT)
232                     .build();
233         } else {
234             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
235                     .setCountOfProvisionFail(ADD_COUNT)
236                     .build();
237         }
238         logd("reportProvisionCount(): " + controllerParam);
239         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
240     }
241 
242     /** Report a counter when an attempt for de-provision is success or not */
reportDeprovisionCount(@onNull @atelliteManager.SatelliteResult int result)243     public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteResult int result) {
244         SatelliteStats.SatelliteControllerParams controllerParam;
245         if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
246             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
247                     .setCountOfDeprovisionSuccess(ADD_COUNT)
248                     .build();
249         } else {
250             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
251                     .setCountOfDeprovisionFail(ADD_COUNT)
252                     .build();
253         }
254         logd("reportDeprovisionCount(): " + controllerParam);
255         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
256     }
257 
258     /**
259      * Report a counter when checking result whether satellite communication is allowed or not for
260      * current location.
261      */
reportAllowedSatelliteAccessCount(boolean isAllowed)262     public void reportAllowedSatelliteAccessCount(boolean isAllowed) {
263         SatelliteStats.SatelliteControllerParams.Builder builder;
264         if (isAllowed) {
265             builder = new SatelliteStats.SatelliteControllerParams.Builder()
266                     .setCountOfAllowedSatelliteAccess(ADD_COUNT);
267         } else {
268             builder = new SatelliteStats.SatelliteControllerParams.Builder()
269                     .setCountOfDisallowedSatelliteAccess(ADD_COUNT);
270         }
271         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
272         logd("reportAllowedSatelliteAccessCount:" + controllerParam);
273         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
274     }
275 
276     /**
277      * Report a counter when checking whether satellite communication for current location is
278      * allowed has failed.
279      */
reportFailedSatelliteAccessCheckCount()280     public void reportFailedSatelliteAccessCheckCount() {
281         SatelliteStats.SatelliteControllerParams controllerParam =
282                 new SatelliteStats.SatelliteControllerParams.Builder()
283                         .setCountOfSatelliteAccessCheckFail(ADD_COUNT).build();
284         logd("reportFailedSatelliteAccessCheckCount:" + controllerParam);
285         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
286     }
287 
288     /** Return the total service up time for satellite service */
289     @VisibleForTesting
captureTotalServiceUpTimeSec()290     public int captureTotalServiceUpTimeSec() {
291         long totalTimeMillis = getElapsedRealtime() - mSatelliteOnTimeMillis;
292         mSatelliteOnTimeMillis = 0;
293         return (int) (totalTimeMillis / 1000);
294     }
295 
296     /** Return the total battery charge time while satellite service is on */
297     @VisibleForTesting
captureTotalBatteryChargeTimeSec()298     public int captureTotalBatteryChargeTimeSec() {
299         int totalTime = mTotalBatteryChargeTimeSec;
300         mTotalBatteryChargeTimeSec = 0;
301         return totalTime;
302     }
303 
304     /** Capture the satellite service on time and register battery monitor */
onSatelliteEnabled()305     public void onSatelliteEnabled() {
306         if (!isSatelliteModemOn()) {
307             mIsSatelliteModemOn = true;
308 
309             startCaptureBatteryLevel();
310 
311             // log the timestamp of the satellite modem power on
312             mSatelliteOnTimeMillis = getElapsedRealtime();
313 
314             // register broadcast receiver for monitoring battery status change
315             IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
316 
317             logd("register BatteryStatusReceiver");
318             mContext.registerReceiver(mBatteryStatusReceiver, filter);
319         }
320     }
321 
322     /** Capture the satellite service off time and de-register battery monitor */
onSatelliteDisabled()323     public void onSatelliteDisabled() {
324         if (isSatelliteModemOn()) {
325             mIsSatelliteModemOn = false;
326 
327             logd("unregister BatteryStatusReceiver");
328             mContext.unregisterReceiver(mBatteryStatusReceiver);
329 
330             int totalServiceUpTime = captureTotalServiceUpTimeSec();
331             int batteryConsumptionPercent = captureTotalBatteryConsumptionPercent(mContext);
332             int totalBatteryChargeTime = captureTotalBatteryChargeTimeSec();
333 
334             // report metrics about service up time and battery
335             SatelliteStats.SatelliteControllerParams controllerParam =
336                     new SatelliteStats.SatelliteControllerParams.Builder()
337                             .setTotalServiceUptimeSec(totalServiceUpTime)
338                             .setTotalBatteryConsumptionPercent(batteryConsumptionPercent)
339                             .setTotalBatteryChargedTimeSec(totalBatteryChargeTime)
340                             .build();
341             logd("onSatelliteDisabled(): " + controllerParam);
342             mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
343         }
344     }
345 
346     /** Log the total battery charging time when satellite service is on */
updateSatelliteBatteryChargeTime(boolean isCharged)347     private void updateSatelliteBatteryChargeTime(boolean isCharged) {
348         logd("updateSatelliteBatteryChargeTime(" + isCharged + ")");
349         // update only when the charge state has changed
350         if (mIsBatteryCharged == null || isCharged != mIsBatteryCharged) {
351             mIsBatteryCharged = isCharged;
352 
353             // When charged, log the start time of battery charging
354             if (isCharged) {
355                 mBatteryChargedStartTime = getElapsedRealtime();
356                 // When discharged, log the accumulated total battery charging time.
357             } else {
358                 mTotalBatteryChargeTimeSec +=
359                         (int) ((getElapsedRealtime() - mBatteryChargedStartTime) / 1000);
360                 mBatteryChargedStartTime = 0;
361             }
362         }
363     }
364 
365     /** Capture the battery level when satellite service is on */
366     @VisibleForTesting
startCaptureBatteryLevel()367     public void startCaptureBatteryLevel() {
368         try {
369             BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class);
370             mBatteryLevelWhenServiceOn =
371                     batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
372             logd("sBatteryLevelWhenServiceOn = " + mBatteryLevelWhenServiceOn);
373         } catch (NullPointerException e) {
374             loge("BatteryManager is null");
375         }
376     }
377 
378     /** Capture the total consumption level when service is off */
379     @VisibleForTesting
captureTotalBatteryConsumptionPercent(Context context)380     public int captureTotalBatteryConsumptionPercent(Context context) {
381         try {
382             BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
383             int currentLevel =
384                     batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
385             return Math.max((mBatteryLevelWhenServiceOn - currentLevel), 0);
386         } catch (NullPointerException e) {
387             loge("BatteryManager is null");
388             return 0;
389         }
390     }
391 
392     /** Capture the latest provisioned state for satellite service */
393     @VisibleForTesting
setIsProvisioned(boolean isProvisioned)394     public void setIsProvisioned(boolean isProvisioned) {
395         logd("setIsProvisioned:" + isProvisioned);
396         mSatelliteStats.onSatelliteControllerMetrics(
397                 new SatelliteStats.SatelliteControllerParams.Builder()
398                         .setIsProvisioned(isProvisioned)
399                         .build());
400     }
401 
402     /** Capture the NB-IoT NTN carrier ID */
setCarrierId(int carrierId)403     public void setCarrierId(int carrierId) {
404         logd("setCarrierId:" + carrierId);
405         mSatelliteStats.onSatelliteControllerMetrics(
406                 new SatelliteStats.SatelliteControllerParams.Builder()
407                         .setCarrierId(carrierId)
408                         .build());
409     }
410 
411     /**
412      * Report a counter when allowed state has changed.
413      */
reportAllowedStateChanged()414     public void reportAllowedStateChanged() {
415         logd("reportAllowedStateChanged:");
416         mSatelliteStats.onSatelliteControllerMetrics(
417                 new SatelliteStats.SatelliteControllerParams.Builder()
418                         .setCountOfSatelliteAllowedStateChangedEvents(ADD_COUNT)
419                         .build());
420     }
421 
422     /**
423      * Report a counter when location query was successful or failed.
424      */
reportLocationQuerySuccessful(boolean result)425     public void reportLocationQuerySuccessful(boolean result) {
426         SatelliteStats.SatelliteControllerParams.Builder builder;
427         if (result) {
428             builder = new SatelliteStats.SatelliteControllerParams.Builder()
429                     .setCountOfSuccessfulLocationQueries(ADD_COUNT);
430         } else {
431             builder = new SatelliteStats.SatelliteControllerParams.Builder()
432                     .setCountOfFailedLocationQueries(ADD_COUNT);
433         }
434         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
435         logd("reportLocationQuerySuccessful:" + controllerParam);
436         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
437     }
438 
439     /**
440      * Report a current version of satellite access config.
441      */
reportCurrentVersionOfSatelliteAccessConfig(int version)442     public void reportCurrentVersionOfSatelliteAccessConfig(int version) {
443         logd("reportCurrentVersionOfSatelliteAccessConfig:" + version);
444         mSatelliteStats.onSatelliteControllerMetrics(
445                 new SatelliteStats.SatelliteControllerParams.Builder()
446                         .setVersionOfSatelliteAccessControl(version)
447                         .build());
448     }
449 
450     /**
451      * Add count when the notification for P2P SMS over satellite avaibility is shown or removed.
452      */
reportP2PSmsEligibilityNotificationsCount(boolean isEligible)453     public void reportP2PSmsEligibilityNotificationsCount(boolean isEligible) {
454         SatelliteStats.SatelliteControllerParams.Builder builder;
455         if (isEligible) {
456             builder = new SatelliteStats.SatelliteControllerParams.Builder()
457                     .setCountOfP2PSmsAvailableNotificationShown(ADD_COUNT);
458         } else {
459             builder = new SatelliteStats.SatelliteControllerParams.Builder()
460                     .setCountOfP2PSmsAvailableNotificationRemoved(ADD_COUNT);
461 
462         }
463         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
464         logd("reportP2PSmsEligibilityNotificationsCount:" + controllerParam);
465         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
466     }
467 
468     /** Capture the latest provisioned state for satellite service */
setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier)469     public void setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
470         logd("setIsNtnOnlyCarrier:" + isNtnOnlyCarrier);
471         mSatelliteStats.onSatelliteControllerMetrics(
472                 new SatelliteStats.SatelliteControllerParams.Builder()
473                         .setIsNtnOnlyCarrier(isNtnOnlyCarrier)
474                         .build());
475     }
476 
477     /** Receives the battery status whether it is in charging or not, update interval is 60 sec. */
478     private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() {
479         private long mLastUpdatedTime = 0;
480         private static final long UPDATE_INTERVAL = 60 * 1000;
481 
482         @Override
483         public void onReceive(Context context, Intent intent) {
484             long elapsedTimeSinceBoot = getElapsedRealtime();
485             if (elapsedTimeSinceBoot - mLastUpdatedTime > UPDATE_INTERVAL) {
486                 mLastUpdatedTime = elapsedTimeSinceBoot;
487                 int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
488                 boolean isCharged = (status == BatteryManager.BATTERY_STATUS_CHARGING);
489                 logd("Battery is charged(" + isCharged + ")");
490                 updateSatelliteBatteryChargeTime(isCharged);
491             }
492         }
493     };
494 
495     @VisibleForTesting
isSatelliteModemOn()496     public boolean isSatelliteModemOn() {
497         return mIsSatelliteModemOn;
498     }
499 
500     @VisibleForTesting
getElapsedRealtime()501     public long getElapsedRealtime() {
502         return SystemClock.elapsedRealtime();
503     }
504 
logd(@onNull String log)505     private static void logd(@NonNull String log) {
506         Log.d(TAG, log);
507     }
508 
loge(@onNull String log)509     private static void loge(@NonNull String log) {
510         Log.e(TAG, log);
511     }
512 }
513