1/*
2 * Copyright (C) 2006 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.phone;
18
19import android.content.Context;
20import android.os.Debug;
21import android.os.Handler;
22import android.os.SystemClock;
23import com.android.internal.telephony.Call;
24import com.android.internal.telephony.Connection;
25import android.util.Log;
26
27import java.io.File;
28import java.util.List;
29
30/**
31 * Helper class used to keep track of various "elapsed time" indications
32 * in the Phone app, and also to start and stop tracing / profiling.
33 */
34public class CallTime extends Handler {
35    private static final String LOG_TAG = "PHONE/CallTime";
36    private static final boolean DBG = false;
37    /* package */ static final boolean PROFILE = true;
38
39    private static final int PROFILE_STATE_NONE = 0;
40    private static final int PROFILE_STATE_READY = 1;
41    private static final int PROFILE_STATE_RUNNING = 2;
42
43    private static int sProfileState = PROFILE_STATE_NONE;
44
45    private Call mCall;
46    private long mLastReportedTime;
47    private boolean mTimerRunning;
48    private long mInterval;
49    private PeriodicTimerCallback mTimerCallback;
50    private OnTickListener mListener;
51
52    interface OnTickListener {
53        void onTickForCallTimeElapsed(long timeElapsed);
54    }
55
56    public CallTime(OnTickListener listener) {
57        mListener = listener;
58        mTimerCallback = new PeriodicTimerCallback();
59    }
60
61    /**
62     * Sets the call timer to "active call" mode, where the timer will
63     * periodically update the UI to show how long the specified call
64     * has been active.
65     *
66     * After calling this you should also call reset() and
67     * periodicUpdateTimer() to get the timer started.
68     */
69    /* package */ void setActiveCallMode(Call call) {
70        if (DBG) log("setActiveCallMode(" + call + ")...");
71        mCall = call;
72
73        // How frequently should we update the UI?
74        mInterval = 1000;  // once per second
75    }
76
77    /* package */ void reset() {
78        if (DBG) log("reset()...");
79        mLastReportedTime = SystemClock.uptimeMillis() - mInterval;
80    }
81
82    /* package */ void periodicUpdateTimer() {
83        if (!mTimerRunning) {
84            mTimerRunning = true;
85
86            long now = SystemClock.uptimeMillis();
87            long nextReport = mLastReportedTime + mInterval;
88
89            while (now >= nextReport) {
90                nextReport += mInterval;
91            }
92
93            if (DBG) log("periodicUpdateTimer() @ " + nextReport);
94            postAtTime(mTimerCallback, nextReport);
95            mLastReportedTime = nextReport;
96
97            if (mCall != null) {
98                Call.State state = mCall.getState();
99
100                if (state == Call.State.ACTIVE) {
101                    updateElapsedTime(mCall);
102                }
103            }
104
105            if (PROFILE && isTraceReady()) {
106                startTrace();
107            }
108        } else {
109            if (DBG) log("periodicUpdateTimer: timer already running, bail");
110        }
111    }
112
113    /* package */ void cancelTimer() {
114        if (DBG) log("cancelTimer()...");
115        removeCallbacks(mTimerCallback);
116        mTimerRunning = false;
117    }
118
119    private void updateElapsedTime(Call call) {
120        if (mListener != null) {
121            long duration = getCallDuration(call);
122            mListener.onTickForCallTimeElapsed(duration / 1000);
123        }
124    }
125
126    /**
127     * Returns a "call duration" value for the specified Call, in msec,
128     * suitable for display in the UI.
129     */
130    /* package */ static long getCallDuration(Call call) {
131        long duration = 0;
132        List connections = call.getConnections();
133        int count = connections.size();
134        Connection c;
135
136        if (count == 1) {
137            c = (Connection) connections.get(0);
138            //duration = (state == Call.State.ACTIVE
139            //            ? c.getDurationMillis() : c.getHoldDurationMillis());
140            duration = c.getDurationMillis();
141        } else {
142            for (int i = 0; i < count; i++) {
143                c = (Connection) connections.get(i);
144                //long t = (state == Call.State.ACTIVE
145                //          ? c.getDurationMillis() : c.getHoldDurationMillis());
146                long t = c.getDurationMillis();
147                if (t > duration) {
148                    duration = t;
149                }
150            }
151        }
152
153        if (DBG) log("updateElapsedTime, count=" + count + ", duration=" + duration);
154        return duration;
155    }
156
157    private static void log(String msg) {
158        Log.d(LOG_TAG, "[CallTime] " + msg);
159    }
160
161    private class PeriodicTimerCallback implements Runnable {
162        PeriodicTimerCallback() {
163
164        }
165
166        public void run() {
167            if (PROFILE && isTraceRunning()) {
168                stopTrace();
169            }
170
171            mTimerRunning = false;
172            periodicUpdateTimer();
173        }
174    }
175
176    static void setTraceReady() {
177        if (sProfileState == PROFILE_STATE_NONE) {
178            sProfileState = PROFILE_STATE_READY;
179            log("trace ready...");
180        } else {
181            log("current trace state = " + sProfileState);
182        }
183    }
184
185    boolean isTraceReady() {
186        return sProfileState == PROFILE_STATE_READY;
187    }
188
189    boolean isTraceRunning() {
190        return sProfileState == PROFILE_STATE_RUNNING;
191    }
192
193    void startTrace() {
194        if (PROFILE & sProfileState == PROFILE_STATE_READY) {
195            // For now, we move away from temp directory in favor of
196            // the application's data directory to store the trace
197            // information (/data/data/com.android.phone).
198            File file = PhoneApp.getInstance().getDir ("phoneTrace", Context.MODE_PRIVATE);
199            if (file.exists() == false) {
200                file.mkdirs();
201            }
202            String baseName = file.getPath() + File.separator + "callstate";
203            String dataFile = baseName + ".data";
204            String keyFile = baseName + ".key";
205
206            file = new File(dataFile);
207            if (file.exists() == true) {
208                file.delete();
209            }
210
211            file = new File(keyFile);
212            if (file.exists() == true) {
213                file.delete();
214            }
215
216            sProfileState = PROFILE_STATE_RUNNING;
217            log("startTrace");
218            Debug.startMethodTracing(baseName, 8 * 1024 * 1024);
219        }
220    }
221
222    void stopTrace() {
223        if (PROFILE) {
224            if (sProfileState == PROFILE_STATE_RUNNING) {
225                sProfileState = PROFILE_STATE_NONE;
226                log("stopTrace");
227                Debug.stopMethodTracing();
228            }
229        }
230    }
231}
232