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 */ 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