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 
17 package com.android.voicedialer;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.os.Build;
22 import android.speech.srec.WaveHeader;
23 import android.text.format.DateFormat;
24 import android.util.Log;
25 
26 import java.io.BufferedWriter;
27 import java.io.ByteArrayOutputStream;
28 import java.io.File;
29 import java.io.FileFilter;
30 import java.io.FileOutputStream;
31 import java.io.FileWriter;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import 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  */
51 public 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      */
isEnabled(Context context)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      */
enable(Context context)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      */
disable(Context context)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      */
RecognizerLogger(Context context)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      */
logLine(String msg)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      */
logNbestHeader()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      */
logContacts(List<VoiceContact> contacts)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      */
logIntents(ArrayList<Intent> intents)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      */
close()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      */
deleteOldest(final String suffix)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      */
logInputStream(final InputStream inputStream, final int sampleRate)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