Home | History | Annotate | Download | only in utils
      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