1/*
2 * Copyright (C) 2009 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.voicedialer;
18
19import android.content.Context;
20import android.content.Intent;
21import android.os.Build;
22import android.speech.srec.WaveHeader;
23import android.text.format.DateFormat;
24import android.util.Log;
25
26import java.io.BufferedWriter;
27import java.io.ByteArrayOutputStream;
28import java.io.File;
29import java.io.FileFilter;
30import java.io.FileOutputStream;
31import java.io.FileWriter;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.List;
38
39/**
40 * This class logs the inputs and results of a recognition session to
41 * the files listed below, which reside in
42 * /data/data/com.android.voicedialer/app_logdir.
43 * The files have the date encoded in the  name so that they will sort in
44 * time order.  The newest RecognizerLogger.MAX_FILES are kept,
45 * and the rest deleted to limit space used in the file system.
46 * <ul>
47 * <li> datename.wav - what the microphone heard.
48 * <li> datename.log - contact list, results, errors, etc.
49 * </ul>
50 */
51public class RecognizerLogger {
52
53    private static final String TAG = "RecognizerLogger";
54
55    private static final String LOGDIR = "logdir";
56    private static final String ENABLED = "enabled";
57
58    private static final int MAX_FILES = 20;
59
60    private final String mDatedPath;
61    private final BufferedWriter mWriter;
62
63    /**
64     * Determine if logging is enabled.  If the
65     * @param context needed to reference the logging directory.
66     * @return true if logging is enabled, determined by the 'enabled' file.
67     */
68    public static boolean isEnabled(Context context) {
69        File dir = context.getDir(LOGDIR, 0);
70        File enabled = new File(dir, ENABLED);
71        return enabled.exists();
72    }
73
74    /**
75     * Enable logging.
76     * @param context needed to reference the logging directory.
77     */
78    public static void enable(Context context) {
79        try {
80            File dir = context.getDir(LOGDIR, 0);
81            File enabled = new File(dir, ENABLED);
82            enabled.createNewFile();
83        }
84        catch (IOException e) {
85            Log.e(TAG, "enableLogging " + e);
86        }
87    }
88
89    /**
90     * Disable logging.
91     * @param context needed to reference the logging directory.
92     */
93    public static void disable(Context context) {
94        try {
95            File dir = context.getDir(LOGDIR, 0);
96            File enabled = new File(dir, ENABLED);
97            enabled.delete();
98        }
99        catch (SecurityException e) {
100            Log.e(TAG, "disableLogging " + e);
101        }
102    }
103
104    /**
105     * Constructor
106     * @param dataDir directory to contain the log files.
107     */
108    public RecognizerLogger(Context context) throws IOException {
109        if (false) Log.d(TAG, "RecognizerLogger");
110
111        // generate new root filename
112        File dir = context.getDir(LOGDIR, 0);
113        mDatedPath = dir.toString() + File.separator + "log_" +
114                DateFormat.format("yyyy_MM_dd_kk_mm_ss",
115                        System.currentTimeMillis());
116
117        // delete oldest files
118        deleteOldest(".wav");
119        deleteOldest(".log");
120
121        // generate new text output log file
122        mWriter = new BufferedWriter(new FileWriter(mDatedPath + ".log"), 8192);
123        mWriter.write(Build.FINGERPRINT);
124        mWriter.newLine();
125    }
126
127    /**
128     * Write a line into the text log file.
129     */
130    public void logLine(String msg) {
131        try {
132            mWriter.write(msg);
133            mWriter.newLine();
134        }
135        catch (IOException e) {
136            Log.e(TAG, "logLine exception: " + e);
137        }
138    }
139
140    /**
141     * Write a header for the NBest lines into the text log file.
142     */
143    public void logNbestHeader() {
144        logLine("Nbest *****************");
145    }
146
147    /**
148     * Write the list of contacts into the text log file.
149     * @param contacts
150     */
151    public void logContacts(List<VoiceContact> contacts) {
152        logLine("Contacts *****************");
153        for (VoiceContact vc : contacts) logLine(vc.toString());
154        try {
155            mWriter.flush();
156        }
157        catch (IOException e) {
158            Log.e(TAG, "logContacts exception: " + e);
159        }
160    }
161
162    /**
163     * Write a list of Intents into the text log file.
164     * @param intents
165     */
166    public void logIntents(ArrayList<Intent> intents) {
167        logLine("Intents *********************");
168        StringBuffer sb = new StringBuffer();
169        for (Intent intent : intents) {
170            logLine(intent.toString() + " " + RecognizerEngine.SENTENCE_EXTRA + "=" +
171                    intent.getStringExtra(RecognizerEngine.SENTENCE_EXTRA));
172        }
173        try {
174            mWriter.flush();
175        }
176        catch (IOException e) {
177            Log.e(TAG, "logIntents exception: " + e);
178        }
179    }
180
181    /**
182     * Close the text log file.
183     * @throws IOException
184     */
185    public void close() throws IOException {
186        mWriter.close();
187    }
188
189    /**
190     * Delete oldest files with a given suffix, if more than MAX_FILES.
191     * @param suffix delete oldest files with this suffix.
192     */
193    private void deleteOldest(final String suffix) {
194        FileFilter ff = new FileFilter() {
195            public boolean accept(File f) {
196                String name = f.getName();
197                return name.startsWith("log_") && name.endsWith(suffix);
198            }
199        };
200        File[] files = (new File(mDatedPath)).getParentFile().listFiles(ff);
201        Arrays.sort(files);
202
203        for (int i = 0; i < files.length - MAX_FILES; i++) {
204            files[i].delete();
205        }
206    }
207
208    /**
209     * InputStream wrapper which will log the contents to a WAV file.
210     * @param inputStream
211     * @param sampleRate
212     * @return
213     */
214    public InputStream logInputStream(final InputStream inputStream, final int sampleRate) {
215        final ByteArrayOutputStream baos = new ByteArrayOutputStream(sampleRate * 2 * 20);
216
217        return new InputStream() {
218
219            public int available() throws IOException {
220                return inputStream.available();
221            }
222
223            public int read(byte[] b, int offset, int length) throws IOException {
224                int rtn = inputStream.read(b, offset, length);
225                if (rtn > 0) baos.write(b, offset, rtn);
226                return rtn;
227            }
228
229            public int read(byte[] b) throws IOException {
230                int rtn = inputStream.read(b);
231                if (rtn > 0) baos.write(b, 0, rtn);
232                return rtn;
233            }
234
235            public int read() throws IOException {
236                int rtn = inputStream.read();
237                if (rtn > 0) baos.write(rtn);
238                return rtn;
239            }
240
241            public long skip(long n) throws IOException {
242                throw new UnsupportedOperationException();
243            }
244
245            public void close() throws IOException {
246                try {
247                    OutputStream out = new FileOutputStream(mDatedPath + ".wav");
248                    try {
249                        byte[] pcm = baos.toByteArray();
250                        WaveHeader hdr = new WaveHeader(WaveHeader.FORMAT_PCM,
251                                (short)1, sampleRate, (short)16, pcm.length);
252                        hdr.write(out);
253                        out.write(pcm);
254                    }
255                    finally {
256                        out.close();
257                    }
258                }
259                finally {
260                    inputStream.close();
261                    baos.close();
262                }
263            }
264        };
265    }
266
267}
268