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