1 /* 2 * Copyright (C) 2016 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.inputmethod.latin.utils; 18 19 import android.content.Intent; 20 import android.content.pm.PackageManager; 21 import android.inputmethodservice.InputMethodService; 22 import android.net.Uri; 23 import android.os.Environment; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.Process; 27 import android.util.Log; 28 import android.view.MotionEvent; 29 30 import com.android.inputmethod.latin.LatinImeLogger; 31 32 import java.io.BufferedReader; 33 import java.io.File; 34 import java.io.FileInputStream; 35 import java.io.FileNotFoundException; 36 import java.io.FileOutputStream; 37 import java.io.FileReader; 38 import java.io.IOException; 39 import java.io.PrintWriter; 40 import java.nio.channels.FileChannel; 41 import java.text.SimpleDateFormat; 42 import java.util.Date; 43 import java.util.Locale; 44 45 public final class UsabilityStudyLogUtils { 46 // TODO: remove code duplication with ResearchLog class 47 private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName(); 48 private static final String FILENAME = "log.txt"; 49 private final Handler mLoggingHandler; 50 private File mFile; 51 private File mDirectory; 52 private InputMethodService mIms; 53 private PrintWriter mWriter; 54 private final Date mDate; 55 private final SimpleDateFormat mDateFormat; 56 57 private UsabilityStudyLogUtils() { 58 mDate = new Date(); 59 mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US); 60 61 HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task", 62 Process.THREAD_PRIORITY_BACKGROUND); 63 handlerThread.start(); 64 mLoggingHandler = new Handler(handlerThread.getLooper()); 65 } 66 67 // Initialization-on-demand holder 68 private static final class OnDemandInitializationHolder { 69 public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils(); 70 } 71 72 public static UsabilityStudyLogUtils getInstance() { 73 return OnDemandInitializationHolder.sInstance; 74 } 75 76 public void init(final InputMethodService ims) { 77 mIms = ims; 78 mDirectory = ims.getFilesDir(); 79 } 80 81 private void createLogFileIfNotExist() { 82 if ((mFile == null || !mFile.exists()) 83 && (mDirectory != null && mDirectory.exists())) { 84 try { 85 mWriter = getPrintWriter(mDirectory, FILENAME, false); 86 } catch (final IOException e) { 87 Log.e(USABILITY_TAG, "Can't create log file."); 88 } 89 } 90 } 91 92 public static void writeBackSpace(final int x, final int y) { 93 UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y); 94 } 95 96 public static void writeChar(final char c, final int x, final int y) { 97 String inputChar = String.valueOf(c); 98 switch (c) { 99 case '\n': 100 inputChar = "<enter>"; 101 break; 102 case '\t': 103 inputChar = "<tab>"; 104 break; 105 case ' ': 106 inputChar = "<space>"; 107 break; 108 } 109 UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y); 110 LatinImeLogger.onPrintAllUsabilityStudyLogs(); 111 } 112 113 public static void writeMotionEvent(final MotionEvent me) { 114 final int action = me.getActionMasked(); 115 final long eventTime = me.getEventTime(); 116 final int pointerCount = me.getPointerCount(); 117 for (int index = 0; index < pointerCount; index++) { 118 final int id = me.getPointerId(index); 119 final int x = (int)me.getX(index); 120 final int y = (int)me.getY(index); 121 final float size = me.getSize(index); 122 final float pressure = me.getPressure(index); 123 124 final String eventTag; 125 switch (action) { 126 case MotionEvent.ACTION_UP: 127 eventTag = "[Up]"; 128 break; 129 case MotionEvent.ACTION_DOWN: 130 eventTag = "[Down]"; 131 break; 132 case MotionEvent.ACTION_POINTER_UP: 133 eventTag = "[PointerUp]"; 134 break; 135 case MotionEvent.ACTION_POINTER_DOWN: 136 eventTag = "[PointerDown]"; 137 break; 138 case MotionEvent.ACTION_MOVE: 139 eventTag = "[Move]"; 140 break; 141 default: 142 eventTag = "[Action" + action + "]"; 143 break; 144 } 145 getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size 146 + "," + pressure); 147 } 148 } 149 150 public void write(final String log) { 151 mLoggingHandler.post(new Runnable() { 152 @Override 153 public void run() { 154 createLogFileIfNotExist(); 155 final long currentTime = System.currentTimeMillis(); 156 mDate.setTime(currentTime); 157 158 final String printString = String.format(Locale.US, "%s\t%d\t%s\n", 159 mDateFormat.format(mDate), currentTime, log); 160 if (LatinImeLogger.sDBG) { 161 Log.d(USABILITY_TAG, "Write: " + log); 162 } 163 mWriter.print(printString); 164 } 165 }); 166 } 167 168 private synchronized String getBufferedLogs() { 169 mWriter.flush(); 170 final StringBuilder sb = new StringBuilder(); 171 final BufferedReader br = getBufferedReader(); 172 String line; 173 try { 174 while ((line = br.readLine()) != null) { 175 sb.append('\n'); 176 sb.append(line); 177 } 178 } catch (final IOException e) { 179 Log.e(USABILITY_TAG, "Can't read log file."); 180 } finally { 181 if (LatinImeLogger.sDBG) { 182 Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString()); 183 } 184 try { 185 br.close(); 186 } catch (final IOException e) { 187 // ignore. 188 } 189 } 190 return sb.toString(); 191 } 192 193 public void emailResearcherLogsAll() { 194 mLoggingHandler.post(new Runnable() { 195 @Override 196 public void run() { 197 final Date date = new Date(); 198 date.setTime(System.currentTimeMillis()); 199 final String currentDateTimeString = 200 new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date); 201 if (mFile == null) { 202 Log.w(USABILITY_TAG, "No internal log file found."); 203 return; 204 } 205 if (mIms.checkCallingOrSelfPermission( 206 android.Manifest.permission.WRITE_EXTERNAL_STORAGE) 207 != PackageManager.PERMISSION_GRANTED) { 208 Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE"); 209 return; 210 } 211 mWriter.flush(); 212 final String destPath = Environment.getExternalStorageDirectory() 213 + "/research-" + currentDateTimeString + ".log"; 214 final File destFile = new File(destPath); 215 try { 216 final FileInputStream srcStream = new FileInputStream(mFile); 217 final FileOutputStream destStream = new FileOutputStream(destFile); 218 final FileChannel src = srcStream.getChannel(); 219 final FileChannel dest = destStream.getChannel(); 220 src.transferTo(0, src.size(), dest); 221 src.close(); 222 srcStream.close(); 223 dest.close(); 224 destStream.close(); 225 } catch (final FileNotFoundException e1) { 226 Log.w(USABILITY_TAG, e1); 227 return; 228 } catch (final IOException e2) { 229 Log.w(USABILITY_TAG, e2); 230 return; 231 } 232 if (!destFile.exists()) { 233 Log.w(USABILITY_TAG, "Dest file doesn't exist."); 234 return; 235 } 236 final Intent intent = new Intent(Intent.ACTION_SEND); 237 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 238 if (LatinImeLogger.sDBG) { 239 Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI()); 240 } 241 intent.setType("text/plain"); 242 intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath)); 243 intent.putExtra(Intent.EXTRA_SUBJECT, 244 "[Research Logs] " + currentDateTimeString); 245 mIms.startActivity(intent); 246 } 247 }); 248 } 249 250 public void printAll() { 251 mLoggingHandler.post(new Runnable() { 252 @Override 253 public void run() { 254 mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0); 255 } 256 }); 257 } 258 259 public void clearAll() { 260 mLoggingHandler.post(new Runnable() { 261 @Override 262 public void run() { 263 if (mFile != null && mFile.exists()) { 264 if (LatinImeLogger.sDBG) { 265 Log.d(USABILITY_TAG, "Delete log file."); 266 } 267 mFile.delete(); 268 mWriter.close(); 269 } 270 } 271 }); 272 } 273 274 private BufferedReader getBufferedReader() { 275 createLogFileIfNotExist(); 276 try { 277 return new BufferedReader(new FileReader(mFile)); 278 } catch (final FileNotFoundException e) { 279 return null; 280 } 281 } 282 283 private PrintWriter getPrintWriter(final File dir, final String filename, 284 final boolean renew) throws IOException { 285 mFile = new File(dir, filename); 286 if (mFile.exists()) { 287 if (renew) { 288 mFile.delete(); 289 } 290 } 291 return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */); 292 } 293 } 294