Analytics.java revision 874c0f8fa95a5da5a82e67c1fe39697883d753eb
1/*
2 * Copyright (C) 2015 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
17package com.android.server.telecom;
18
19import android.telecom.DisconnectCause;
20import android.telecom.ParcelableCallAnalytics;
21import android.telecom.TelecomAnalytics;
22import android.util.SparseArray;
23
24import com.android.internal.annotations.VisibleForTesting;
25import com.android.internal.util.IndentingPrintWriter;
26
27import java.util.ArrayList;
28import java.util.Collections;
29import java.util.HashMap;
30import java.util.LinkedList;
31import java.util.List;
32import java.util.Map;
33import java.util.stream.Collectors;
34
35import static android.telecom.ParcelableCallAnalytics.AnalyticsEvent;
36import static android.telecom.TelecomAnalytics.SessionTiming;
37
38/**
39 * A class that collects and stores data on how calls are being made, in order to
40 * aggregate these into useful statistics.
41 */
42public class Analytics {
43    public static final Map<String, Integer> sLogEventToAnalyticsEvent =
44            new HashMap<String, Integer>() {{
45                put(Log.Events.SET_SELECT_PHONE_ACCOUNT, AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT);
46                put(Log.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD);
47                put(Log.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD);
48                put(Log.Events.SWAP, AnalyticsEvent.SWAP);
49                put(Log.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING);
50                put(Log.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH);
51                put(Log.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE);
52                put(Log.Events.SET_PARENT, AnalyticsEvent.SET_PARENT);
53                put(Log.Events.MUTE, AnalyticsEvent.MUTE);
54                put(Log.Events.UNMUTE, AnalyticsEvent.UNMUTE);
55                put(Log.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT);
56                put(Log.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE);
57                put(Log.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET);
58                put(Log.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER);
59                put(Log.Events.SILENCE, AnalyticsEvent.SILENCE);
60                put(Log.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED);
61                put(Log.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED);
62                put(Log.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED);
63                put(Log.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD);
64                put(Log.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD);
65                put(Log.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL);
66                put(Log.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT);
67                put(Log.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT);
68                put(Log.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE);
69                put(Log.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED);
70                put(Log.Events.SET_HOLD, AnalyticsEvent.SET_HOLD);
71                put(Log.Events.SET_DIALING, AnalyticsEvent.SET_DIALING);
72                put(Log.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION);
73                put(Log.Events.BIND_CS, AnalyticsEvent.BIND_CS);
74                put(Log.Events.CS_BOUND, AnalyticsEvent.CS_BOUND);
75                put(Log.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT);
76                put(Log.Events.DIRECT_TO_VM_INITIATED, AnalyticsEvent.DIRECT_TO_VM_INITIATED);
77                put(Log.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED);
78                put(Log.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED);
79                put(Log.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED);
80                put(Log.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT);
81            }};
82
83    public static final Map<String, Integer> sLogSessionToSessionId =
84            new HashMap<String, Integer> () {{
85                put(Log.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL);
86                put(Log.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL);
87                put(Log.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL);
88                put(Log.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL);
89                put(Log.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL);
90                put(Log.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE);
91                put(Log.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE);
92                put(Log.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE);
93                put(Log.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
94                        SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
95                put(Log.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE);
96                put(Log.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING);
97                put(Log.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING);
98                put(Log.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED);
99                put(Log.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD);
100                put(Log.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL);
101                put(Log.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED);
102                put(Log.Sessions.CSW_ADD_CONFERENCE_CALL, SessionTiming.CSW_ADD_CONFERENCE_CALL);
103
104            }};
105
106    public static final Map<String, Integer> sLogEventTimingToAnalyticsEventTiming =
107            new HashMap<String, Integer>() {{
108                put(Log.Events.Timings.ACCEPT_TIMING,
109                        ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING);
110                put(Log.Events.Timings.REJECT_TIMING,
111                        ParcelableCallAnalytics.EventTiming.REJECT_TIMING);
112                put(Log.Events.Timings.DISCONNECT_TIMING,
113                        ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING);
114                put(Log.Events.Timings.HOLD_TIMING,
115                        ParcelableCallAnalytics.EventTiming.HOLD_TIMING);
116                put(Log.Events.Timings.UNHOLD_TIMING,
117                        ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING);
118                put(Log.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
119                        ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING);
120                put(Log.Events.Timings.BIND_CS_TIMING,
121                        ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING);
122                put(Log.Events.Timings.SCREENING_COMPLETED_TIMING,
123                        ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING);
124                put(Log.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
125                        ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING);
126                put(Log.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
127                        ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING);
128                put(Log.Events.Timings.FILTERING_COMPLETED_TIMING,
129                        ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING);
130                put(Log.Events.Timings.FILTERING_TIMED_OUT_TIMING,
131                        ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING);
132            }};
133
134    public static final Map<Integer, String> sSessionIdToLogSession = new HashMap<>();
135    static {
136        for (Map.Entry<String, Integer> e : sLogSessionToSessionId.entrySet()) {
137            sSessionIdToLogSession.put(e.getValue(), e.getKey());
138        }
139    }
140
141    public static class CallInfo {
142        public void setCallStartTime(long startTime) {
143        }
144
145        public void setCallEndTime(long endTime) {
146        }
147
148        public void setCallIsAdditional(boolean isAdditional) {
149        }
150
151        public void setCallIsInterrupted(boolean isInterrupted) {
152        }
153
154        public void setCallDisconnectCause(DisconnectCause disconnectCause) {
155        }
156
157        public void addCallTechnology(int callTechnology) {
158        }
159
160        public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
161        }
162
163        public void setCallConnectionService(String connectionServiceName) {
164        }
165
166        public void setCallEvents(Log.CallEventRecord records) {
167        }
168    }
169
170    /**
171     * A class that holds data associated with a call.
172     */
173    @VisibleForTesting
174    public static class CallInfoImpl extends CallInfo {
175        public String callId;
176        public long startTime;  // start time in milliseconds since the epoch. 0 if not yet set.
177        public long endTime;  // end time in milliseconds since the epoch. 0 if not yet set.
178        public int callDirection;  // one of UNKNOWN_DIRECTION, INCOMING_DIRECTION,
179                                   // or OUTGOING_DIRECTION.
180        public boolean isAdditionalCall = false;  // true if the call came in while another call was
181                                                  // in progress or if the user dialed this call
182                                                  // while in the middle of another call.
183        public boolean isInterrupted = false;  // true if the call was interrupted by an incoming
184                                               // or outgoing call.
185        public int callTechnologies;  // bitmask denoting which technologies a call used.
186
187        // true if the Telecom Call object was created from an existing connection via
188        // CallsManager#createCallForExistingConnection, for example, by ImsConference.
189        public boolean createdFromExistingConnection = false;
190
191        public DisconnectCause callTerminationReason;
192        public String connectionService;
193        public boolean isEmergency = false;
194
195        public Log.CallEventRecord callEvents;
196
197        CallInfoImpl(String callId, int callDirection) {
198            this.callId = callId;
199            startTime = 0;
200            endTime = 0;
201            this.callDirection = callDirection;
202            callTechnologies = 0;
203            connectionService = "";
204        }
205
206        CallInfoImpl(CallInfoImpl other) {
207            this.callId = other.callId;
208            this.startTime = other.startTime;
209            this.endTime = other.endTime;
210            this.callDirection = other.callDirection;
211            this.isAdditionalCall = other.isAdditionalCall;
212            this.isInterrupted = other.isInterrupted;
213            this.callTechnologies = other.callTechnologies;
214            this.createdFromExistingConnection = other.createdFromExistingConnection;
215            this.connectionService = other.connectionService;
216            this.isEmergency = other.isEmergency;
217            this.callEvents = other.callEvents;
218
219            if (other.callTerminationReason != null) {
220                this.callTerminationReason = new DisconnectCause(
221                        other.callTerminationReason.getCode(),
222                        other.callTerminationReason.getLabel(),
223                        other.callTerminationReason.getDescription(),
224                        other.callTerminationReason.getReason(),
225                        other.callTerminationReason.getTone());
226            } else {
227                this.callTerminationReason = null;
228            }
229        }
230
231        @Override
232        public void setCallStartTime(long startTime) {
233            Log.d(TAG, "setting startTime for call " + callId + " to " + startTime);
234            this.startTime = startTime;
235        }
236
237        @Override
238        public void setCallEndTime(long endTime) {
239            Log.d(TAG, "setting endTime for call " + callId + " to " + endTime);
240            this.endTime = endTime;
241        }
242
243        @Override
244        public void setCallIsAdditional(boolean isAdditional) {
245            Log.d(TAG, "setting isAdditional for call " + callId + " to " + isAdditional);
246            this.isAdditionalCall = isAdditional;
247        }
248
249        @Override
250        public void setCallIsInterrupted(boolean isInterrupted) {
251            Log.d(TAG, "setting isInterrupted for call " + callId + " to " + isInterrupted);
252            this.isInterrupted = isInterrupted;
253        }
254
255        @Override
256        public void addCallTechnology(int callTechnology) {
257            Log.d(TAG, "adding callTechnology for call " + callId + ": " + callTechnology);
258            this.callTechnologies |= callTechnology;
259        }
260
261        @Override
262        public void setCallDisconnectCause(DisconnectCause disconnectCause) {
263            Log.d(TAG, "setting disconnectCause for call " + callId + " to " + disconnectCause);
264            this.callTerminationReason = disconnectCause;
265        }
266
267        @Override
268        public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
269            Log.d(TAG, "setting createdFromExistingConnection for call " + callId + " to "
270                    + createdFromExistingConnection);
271            this.createdFromExistingConnection = createdFromExistingConnection;
272        }
273
274        @Override
275        public void setCallConnectionService(String connectionServiceName) {
276            Log.d(TAG, "setting connection service for call " + callId + ": "
277                    + connectionServiceName);
278            this.connectionService = connectionServiceName;
279        }
280
281        @Override
282        public void setCallEvents(Log.CallEventRecord records) {
283            this.callEvents = records;
284        }
285
286        @Override
287        public String toString() {
288            return "{\n"
289                    + "    startTime: " + startTime + '\n'
290                    + "    endTime: " + endTime + '\n'
291                    + "    direction: " + getCallDirectionString() + '\n'
292                    + "    isAdditionalCall: " + isAdditionalCall + '\n'
293                    + "    isInterrupted: " + isInterrupted + '\n'
294                    + "    callTechnologies: " + getCallTechnologiesAsString() + '\n'
295                    + "    callTerminationReason: " + getCallDisconnectReasonString() + '\n'
296                    + "    connectionService: " + connectionService + '\n'
297                    + "}\n";
298        }
299
300        public ParcelableCallAnalytics toParcelableAnalytics() {
301            // Rounds up to the nearest second.
302            long callDuration = (endTime == 0 || startTime == 0) ? 0 : endTime - startTime;
303            callDuration += (callDuration % MILLIS_IN_1_SECOND == 0) ?
304                    0 : (MILLIS_IN_1_SECOND - callDuration % MILLIS_IN_1_SECOND);
305
306            List<AnalyticsEvent> events;
307            List<ParcelableCallAnalytics.EventTiming> timings;
308            if (callEvents != null) {
309                events = convertLogEventsToAnalyticsEvents(callEvents.getEvents());
310                timings = callEvents.extractEventTimings().stream()
311                        .map(Analytics::logEventTimingToAnalyticsEventTiming)
312                        .collect(Collectors.toList());
313            } else {
314                events = Collections.emptyList();
315                timings = Collections.emptyList();
316            }
317            return new ParcelableCallAnalytics(
318                    // rounds down to nearest 5 minute mark
319                    startTime - startTime % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES,
320                    callDuration,
321                    callDirection,
322                    isAdditionalCall,
323                    isInterrupted,
324                    callTechnologies,
325                    callTerminationReason == null ?
326                            ParcelableCallAnalytics.STILL_CONNECTED :
327                            callTerminationReason.getCode(),
328                    isEmergency,
329                    connectionService,
330                    createdFromExistingConnection,
331                    events,
332                    timings);
333        }
334
335        private String getCallDirectionString() {
336            switch (callDirection) {
337                case UNKNOWN_DIRECTION:
338                    return "UNKNOWN";
339                case INCOMING_DIRECTION:
340                    return "INCOMING";
341                case OUTGOING_DIRECTION:
342                    return "OUTGOING";
343                default:
344                    return "UNKNOWN";
345            }
346        }
347
348        private String getCallTechnologiesAsString() {
349            StringBuilder s = new StringBuilder();
350            s.append('[');
351            if ((callTechnologies & CDMA_PHONE) != 0) s.append("CDMA ");
352            if ((callTechnologies & GSM_PHONE) != 0) s.append("GSM ");
353            if ((callTechnologies & SIP_PHONE) != 0) s.append("SIP ");
354            if ((callTechnologies & IMS_PHONE) != 0) s.append("IMS ");
355            if ((callTechnologies & THIRD_PARTY_PHONE) != 0) s.append("THIRD_PARTY ");
356            s.append(']');
357            return s.toString();
358        }
359
360        private String getCallDisconnectReasonString() {
361            if (callTerminationReason != null) {
362                return callTerminationReason.toString();
363            } else {
364                return "NOT SET";
365            }
366        }
367    }
368    public static final String TAG = "TelecomAnalytics";
369
370    // Constants for call direction
371    public static final int UNKNOWN_DIRECTION = ParcelableCallAnalytics.CALLTYPE_UNKNOWN;
372    public static final int INCOMING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_INCOMING;
373    public static final int OUTGOING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_OUTGOING;
374
375    // Constants for call technology
376    public static final int CDMA_PHONE = ParcelableCallAnalytics.CDMA_PHONE;
377    public static final int GSM_PHONE = ParcelableCallAnalytics.GSM_PHONE;
378    public static final int IMS_PHONE = ParcelableCallAnalytics.IMS_PHONE;
379    public static final int SIP_PHONE = ParcelableCallAnalytics.SIP_PHONE;
380    public static final int THIRD_PARTY_PHONE = ParcelableCallAnalytics.THIRD_PARTY_PHONE;
381
382    public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND;
383
384    private static final Object sLock = new Object(); // Coarse lock for all of analytics
385    private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>();
386    private static final List<SessionTiming> sSessionTimings = new LinkedList<>();
387
388    public static void addSessionTiming(String sessionName, long time) {
389        if (sLogSessionToSessionId.containsKey(sessionName)) {
390            synchronized (sLock) {
391                sSessionTimings.add(new SessionTiming(sLogSessionToSessionId.get(sessionName),
392                        time));
393            }
394        }
395    }
396
397    public static CallInfo initiateCallAnalytics(String callId, int direction) {
398        Log.d(TAG, "Starting analytics for call " + callId);
399        CallInfoImpl callInfo = new CallInfoImpl(callId, direction);
400        synchronized (sLock) {
401            sCallIdToInfo.put(callId, callInfo);
402        }
403        return callInfo;
404    }
405
406    public static TelecomAnalytics dumpToParcelableAnalytics() {
407        List<ParcelableCallAnalytics> calls = new LinkedList<>();
408        List<SessionTiming> sessionTimings = new LinkedList<>();
409        synchronized (sLock) {
410            calls.addAll(sCallIdToInfo.values().stream()
411                    .map(CallInfoImpl::toParcelableAnalytics)
412                    .collect(Collectors.toList()));
413            sessionTimings.addAll(sSessionTimings);
414            sCallIdToInfo.clear();
415            sSessionTimings.clear();
416        }
417        return new TelecomAnalytics(sessionTimings, calls);
418    }
419
420    public static void dump(IndentingPrintWriter writer) {
421        synchronized (sLock) {
422            int prefixLength = CallsManager.TELECOM_CALL_ID_PREFIX.length();
423            List<String> callIds = new ArrayList<>(sCallIdToInfo.keySet());
424            // Sort the analytics in increasing order of call IDs
425            Collections.sort(callIds, (id1, id2) -> {
426                int i1, i2;
427                try {
428                    i1 = Integer.valueOf(id1.substring(prefixLength));
429                    i2 = Integer.valueOf(id2.substring(prefixLength));
430                } catch (NumberFormatException e) {
431                    return 0;
432                }
433                return i1 - i2;
434            });
435
436            for (String callId : callIds) {
437                writer.printf("Call %s: ", callId);
438                writer.println(sCallIdToInfo.get(callId).toString());
439            }
440
441            Map<Integer, Double> averageTimings = SessionTiming.averageTimings(sSessionTimings);
442            averageTimings.entrySet().stream()
443                    .filter(e -> sSessionIdToLogSession.containsKey(e.getKey()))
444                    .forEach(e -> writer.printf("%s: %.2f\n",
445                            sSessionIdToLogSession.get(e.getKey()), e.getValue()));
446        }
447    }
448
449    public static void reset() {
450        synchronized (sLock) {
451            sCallIdToInfo.clear();
452        }
453    }
454
455    /**
456     * Returns a copy of callIdToInfo. Use only for testing.
457     */
458    @VisibleForTesting
459    public static Map<String, CallInfoImpl> cloneData() {
460        synchronized (sLock) {
461            Map<String, CallInfoImpl> result = new HashMap<>(sCallIdToInfo.size());
462            for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) {
463                result.put(entry.getKey(), new CallInfoImpl(entry.getValue()));
464            }
465            return result;
466        }
467    }
468
469    private static List<AnalyticsEvent> convertLogEventsToAnalyticsEvents(
470            List<Log.CallEvent> logEvents) {
471        long timeOfLastEvent = -1;
472        ArrayList<AnalyticsEvent> events = new ArrayList<>(logEvents.size());
473        for (Log.CallEvent logEvent : logEvents) {
474            if (sLogEventToAnalyticsEvent.containsKey(logEvent.eventId)) {
475                int analyticsEventId = sLogEventToAnalyticsEvent.get(logEvent.eventId);
476                long timeSinceLastEvent =
477                        timeOfLastEvent < 0 ? -1 : logEvent.time - timeOfLastEvent;
478                events.add(new AnalyticsEvent(
479                        analyticsEventId,
480                        roundToOneSigFig(timeSinceLastEvent)
481                ));
482                timeOfLastEvent = logEvent.time;
483            }
484        }
485        return events;
486    }
487
488    private static ParcelableCallAnalytics.EventTiming logEventTimingToAnalyticsEventTiming(
489            Log.CallEventRecord.EventTiming logEventTiming) {
490        int analyticsEventTimingName =
491                sLogEventTimingToAnalyticsEventTiming.containsKey(logEventTiming.name) ?
492                        sLogEventTimingToAnalyticsEventTiming.get(logEventTiming.name) :
493                        ParcelableCallAnalytics.EventTiming.INVALID;
494        return new ParcelableCallAnalytics.EventTiming(analyticsEventTimingName,
495                (long) logEventTiming.time);
496    }
497
498    @VisibleForTesting
499    public static long roundToOneSigFig(long val)  {
500        if (val == 0) {
501            return val;
502        }
503        int logVal = (int) Math.floor(Math.log10(val < 0 ? -val : val));
504        double s = Math.pow(10, logVal);
505        double dec =  val / s;
506        return (long) (Math.round(dec) * s);
507    }
508}
509