Home | History | Annotate | Download | only in latin
      1 /*
      2  * Copyright (C) 2010 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;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.pm.PackageManager;
     22 import android.content.res.Resources;
     23 import android.inputmethodservice.InputMethodService;
     24 import android.net.Uri;
     25 import android.os.AsyncTask;
     26 import android.os.Build;
     27 import android.os.Environment;
     28 import android.os.Handler;
     29 import android.os.HandlerThread;
     30 import android.os.Process;
     31 import android.text.TextUtils;
     32 import android.text.format.DateUtils;
     33 import android.util.Log;
     34 
     35 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
     36 
     37 import java.io.BufferedReader;
     38 import java.io.File;
     39 import java.io.FileInputStream;
     40 import java.io.FileNotFoundException;
     41 import java.io.FileOutputStream;
     42 import java.io.FileReader;
     43 import java.io.IOException;
     44 import java.io.PrintWriter;
     45 import java.nio.channels.FileChannel;
     46 import java.text.SimpleDateFormat;
     47 import java.util.Collections;
     48 import java.util.Date;
     49 import java.util.HashMap;
     50 import java.util.Map;
     51 
     52 public class Utils {
     53     private Utils() {
     54         // This utility class is not publicly instantiable.
     55     }
     56 
     57     /**
     58      * Cancel an {@link AsyncTask}.
     59      *
     60      * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
     61      *        task should be interrupted; otherwise, in-progress tasks are allowed
     62      *        to complete.
     63      */
     64     public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
     65         if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
     66             task.cancel(mayInterruptIfRunning);
     67         }
     68     }
     69 
     70     public static class GCUtils {
     71         private static final String GC_TAG = GCUtils.class.getSimpleName();
     72         public static final int GC_TRY_COUNT = 2;
     73         // GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
     74         // GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
     75         public static final int GC_TRY_LOOP_MAX = 5;
     76         private static final long GC_INTERVAL = DateUtils.SECOND_IN_MILLIS;
     77         private static GCUtils sInstance = new GCUtils();
     78         private int mGCTryCount = 0;
     79 
     80         public static GCUtils getInstance() {
     81             return sInstance;
     82         }
     83 
     84         public void reset() {
     85             mGCTryCount = 0;
     86         }
     87 
     88         public boolean tryGCOrWait(String metaData, Throwable t) {
     89             if (mGCTryCount == 0) {
     90                 System.gc();
     91             }
     92             if (++mGCTryCount > GC_TRY_COUNT) {
     93                 LatinImeLogger.logOnException(metaData, t);
     94                 return false;
     95             } else {
     96                 try {
     97                     Thread.sleep(GC_INTERVAL);
     98                     return true;
     99                 } catch (InterruptedException e) {
    100                     Log.e(GC_TAG, "Sleep was interrupted.");
    101                     LatinImeLogger.logOnException(metaData, t);
    102                     return false;
    103                 }
    104             }
    105         }
    106     }
    107 
    108     /* package */ static class RingCharBuffer {
    109         private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
    110         private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
    111         private static final int INVALID_COORDINATE = -2;
    112         /* package */ static final int BUFSIZE = 20;
    113         private InputMethodService mContext;
    114         private boolean mEnabled = false;
    115         private int mEnd = 0;
    116         /* package */ int mLength = 0;
    117         private char[] mCharBuf = new char[BUFSIZE];
    118         private int[] mXBuf = new int[BUFSIZE];
    119         private int[] mYBuf = new int[BUFSIZE];
    120 
    121         private RingCharBuffer() {
    122             // Intentional empty constructor for singleton.
    123         }
    124         public static RingCharBuffer getInstance() {
    125             return sRingCharBuffer;
    126         }
    127         public static RingCharBuffer init(InputMethodService context, boolean enabled,
    128                 boolean usabilityStudy) {
    129             if (!(enabled || usabilityStudy)) return null;
    130             sRingCharBuffer.mContext = context;
    131             sRingCharBuffer.mEnabled = true;
    132             UsabilityStudyLogUtils.getInstance().init(context);
    133             return sRingCharBuffer;
    134         }
    135         private static int normalize(int in) {
    136             int ret = in % BUFSIZE;
    137             return ret < 0 ? ret + BUFSIZE : ret;
    138         }
    139         // TODO: accept code points
    140         public void push(char c, int x, int y) {
    141             if (!mEnabled) return;
    142             mCharBuf[mEnd] = c;
    143             mXBuf[mEnd] = x;
    144             mYBuf[mEnd] = y;
    145             mEnd = normalize(mEnd + 1);
    146             if (mLength < BUFSIZE) {
    147                 ++mLength;
    148             }
    149         }
    150         public char pop() {
    151             if (mLength < 1) {
    152                 return PLACEHOLDER_DELIMITER_CHAR;
    153             } else {
    154                 mEnd = normalize(mEnd - 1);
    155                 --mLength;
    156                 return mCharBuf[mEnd];
    157             }
    158         }
    159         public char getBackwardNthChar(int n) {
    160             if (mLength <= n || n < 0) {
    161                 return PLACEHOLDER_DELIMITER_CHAR;
    162             } else {
    163                 return mCharBuf[normalize(mEnd - n - 1)];
    164             }
    165         }
    166         public int getPreviousX(char c, int back) {
    167             int index = normalize(mEnd - 2 - back);
    168             if (mLength <= back
    169                     || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
    170                 return INVALID_COORDINATE;
    171             } else {
    172                 return mXBuf[index];
    173             }
    174         }
    175         public int getPreviousY(char c, int back) {
    176             int index = normalize(mEnd - 2 - back);
    177             if (mLength <= back
    178                     || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
    179                 return INVALID_COORDINATE;
    180             } else {
    181                 return mYBuf[index];
    182             }
    183         }
    184         public String getLastWord(int ignoreCharCount) {
    185             StringBuilder sb = new StringBuilder();
    186             int i = ignoreCharCount;
    187             for (; i < mLength; ++i) {
    188                 char c = mCharBuf[normalize(mEnd - 1 - i)];
    189                 if (!((LatinIME)mContext).isWordSeparator(c)) {
    190                     break;
    191                 }
    192             }
    193             for (; i < mLength; ++i) {
    194                 char c = mCharBuf[normalize(mEnd - 1 - i)];
    195                 if (!((LatinIME)mContext).isWordSeparator(c)) {
    196                     sb.append(c);
    197                 } else {
    198                     break;
    199                 }
    200             }
    201             return sb.reverse().toString();
    202         }
    203         public void reset() {
    204             mLength = 0;
    205         }
    206     }
    207 
    208     // Get the current stack trace
    209     public static String getStackTrace() {
    210         StringBuilder sb = new StringBuilder();
    211         try {
    212             throw new RuntimeException();
    213         } catch (RuntimeException e) {
    214             StackTraceElement[] frames = e.getStackTrace();
    215             // Start at 1 because the first frame is here and we don't care about it
    216             for (int j = 1; j < frames.length; ++j) sb.append(frames[j].toString() + "\n");
    217         }
    218         return sb.toString();
    219     }
    220 
    221     public static class UsabilityStudyLogUtils {
    222         // TODO: remove code duplication with ResearchLog class
    223         private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
    224         private static final String FILENAME = "log.txt";
    225         private final Handler mLoggingHandler;
    226         private File mFile;
    227         private File mDirectory;
    228         private InputMethodService mIms;
    229         private PrintWriter mWriter;
    230         private final Date mDate;
    231         private final SimpleDateFormat mDateFormat;
    232 
    233         private UsabilityStudyLogUtils() {
    234             mDate = new Date();
    235             mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ");
    236 
    237             HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
    238                     Process.THREAD_PRIORITY_BACKGROUND);
    239             handlerThread.start();
    240             mLoggingHandler = new Handler(handlerThread.getLooper());
    241         }
    242 
    243         // Initialization-on-demand holder
    244         private static class OnDemandInitializationHolder {
    245             public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
    246         }
    247 
    248         public static UsabilityStudyLogUtils getInstance() {
    249             return OnDemandInitializationHolder.sInstance;
    250         }
    251 
    252         public void init(InputMethodService ims) {
    253             mIms = ims;
    254             mDirectory = ims.getFilesDir();
    255         }
    256 
    257         private void createLogFileIfNotExist() {
    258             if ((mFile == null || !mFile.exists())
    259                     && (mDirectory != null && mDirectory.exists())) {
    260                 try {
    261                     mWriter = getPrintWriter(mDirectory, FILENAME, false);
    262                 } catch (IOException e) {
    263                     Log.e(USABILITY_TAG, "Can't create log file.");
    264                 }
    265             }
    266         }
    267 
    268         public static void writeBackSpace(int x, int y) {
    269             UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
    270         }
    271 
    272         public void writeChar(char c, int x, int y) {
    273             String inputChar = String.valueOf(c);
    274             switch (c) {
    275                 case '\n':
    276                     inputChar = "<enter>";
    277                     break;
    278                 case '\t':
    279                     inputChar = "<tab>";
    280                     break;
    281                 case ' ':
    282                     inputChar = "<space>";
    283                     break;
    284             }
    285             UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
    286             LatinImeLogger.onPrintAllUsabilityStudyLogs();
    287         }
    288 
    289         public void write(final String log) {
    290             mLoggingHandler.post(new Runnable() {
    291                 @Override
    292                 public void run() {
    293                     createLogFileIfNotExist();
    294                     final long currentTime = System.currentTimeMillis();
    295                     mDate.setTime(currentTime);
    296 
    297                     final String printString = String.format("%s\t%d\t%s\n",
    298                             mDateFormat.format(mDate), currentTime, log);
    299                     if (LatinImeLogger.sDBG) {
    300                         Log.d(USABILITY_TAG, "Write: " + log);
    301                     }
    302                     mWriter.print(printString);
    303                 }
    304             });
    305         }
    306 
    307         private synchronized String getBufferedLogs() {
    308             mWriter.flush();
    309             StringBuilder sb = new StringBuilder();
    310             BufferedReader br = getBufferedReader();
    311             String line;
    312             try {
    313                 while ((line = br.readLine()) != null) {
    314                     sb.append('\n');
    315                     sb.append(line);
    316                 }
    317             } catch (IOException e) {
    318                 Log.e(USABILITY_TAG, "Can't read log file.");
    319             } finally {
    320                 if (LatinImeLogger.sDBG) {
    321                     Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
    322                 }
    323                 try {
    324                     br.close();
    325                 } catch (IOException e) {
    326                     // ignore.
    327                 }
    328             }
    329             return sb.toString();
    330         }
    331 
    332         public void emailResearcherLogsAll() {
    333             mLoggingHandler.post(new Runnable() {
    334                 @Override
    335                 public void run() {
    336                     final Date date = new Date();
    337                     date.setTime(System.currentTimeMillis());
    338                     final String currentDateTimeString =
    339                             new SimpleDateFormat("yyyyMMdd-HHmmssZ").format(date);
    340                     if (mFile == null) {
    341                         Log.w(USABILITY_TAG, "No internal log file found.");
    342                         return;
    343                     }
    344                     if (mIms.checkCallingOrSelfPermission(
    345                                 android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
    346                                         != PackageManager.PERMISSION_GRANTED) {
    347                         Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
    348                         return;
    349                     }
    350                     mWriter.flush();
    351                     final String destPath = Environment.getExternalStorageDirectory()
    352                             + "/research-" + currentDateTimeString + ".log";
    353                     final File destFile = new File(destPath);
    354                     try {
    355                         final FileChannel src = (new FileInputStream(mFile)).getChannel();
    356                         final FileChannel dest = (new FileOutputStream(destFile)).getChannel();
    357                         src.transferTo(0, src.size(), dest);
    358                         src.close();
    359                         dest.close();
    360                     } catch (FileNotFoundException e1) {
    361                         Log.w(USABILITY_TAG, e1);
    362                         return;
    363                     } catch (IOException e2) {
    364                         Log.w(USABILITY_TAG, e2);
    365                         return;
    366                     }
    367                     if (destFile == null || !destFile.exists()) {
    368                         Log.w(USABILITY_TAG, "Dest file doesn't exist.");
    369                         return;
    370                     }
    371                     final Intent intent = new Intent(Intent.ACTION_SEND);
    372                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    373                     if (LatinImeLogger.sDBG) {
    374                         Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
    375                     }
    376                     intent.setType("text/plain");
    377                     intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
    378                     intent.putExtra(Intent.EXTRA_SUBJECT,
    379                             "[Research Logs] " + currentDateTimeString);
    380                     mIms.startActivity(intent);
    381                 }
    382             });
    383         }
    384 
    385         public void printAll() {
    386             mLoggingHandler.post(new Runnable() {
    387                 @Override
    388                 public void run() {
    389                     mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
    390                 }
    391             });
    392         }
    393 
    394         public void clearAll() {
    395             mLoggingHandler.post(new Runnable() {
    396                 @Override
    397                 public void run() {
    398                     if (mFile != null && mFile.exists()) {
    399                         if (LatinImeLogger.sDBG) {
    400                             Log.d(USABILITY_TAG, "Delete log file.");
    401                         }
    402                         mFile.delete();
    403                         mWriter.close();
    404                     }
    405                 }
    406             });
    407         }
    408 
    409         private BufferedReader getBufferedReader() {
    410             createLogFileIfNotExist();
    411             try {
    412                 return new BufferedReader(new FileReader(mFile));
    413             } catch (FileNotFoundException e) {
    414                 return null;
    415             }
    416         }
    417 
    418         private PrintWriter getPrintWriter(
    419                 File dir, String filename, boolean renew) throws IOException {
    420             mFile = new File(dir, filename);
    421             if (mFile.exists()) {
    422                 if (renew) {
    423                     mFile.delete();
    424                 }
    425             }
    426             return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
    427         }
    428     }
    429 
    430     public static float getDipScale(Context context) {
    431         final float scale = context.getResources().getDisplayMetrics().density;
    432         return scale;
    433     }
    434 
    435     /** Convert pixel to DIP */
    436     public static int dipToPixel(float scale, int dip) {
    437         return (int) (dip * scale + 0.5);
    438     }
    439 
    440     public static class Stats {
    441         public static void onNonSeparator(final char code, final int x,
    442                 final int y) {
    443             RingCharBuffer.getInstance().push(code, x, y);
    444             LatinImeLogger.logOnInputChar();
    445         }
    446 
    447         public static void onSeparator(final int code, final int x,
    448                 final int y) {
    449             // TODO: accept code points
    450             RingCharBuffer.getInstance().push((char)code, x, y);
    451             LatinImeLogger.logOnInputSeparator();
    452         }
    453 
    454         public static void onAutoCorrection(final String typedWord, final String correctedWord,
    455                 final int separatorCode) {
    456             if (TextUtils.isEmpty(typedWord)) return;
    457             LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, separatorCode);
    458         }
    459 
    460         public static void onAutoCorrectionCancellation() {
    461             LatinImeLogger.logOnAutoCorrectionCancelled();
    462         }
    463     }
    464 
    465     public static String getDebugInfo(final SuggestedWords suggestions, final int pos) {
    466         if (!LatinImeLogger.sDBG) return null;
    467         final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
    468         if (wordInfo == null) return null;
    469         final String info = wordInfo.getDebugString();
    470         if (TextUtils.isEmpty(info)) return null;
    471         return info;
    472     }
    473 
    474     private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
    475     private static final HashMap<String, String> sDeviceOverrideValueMap =
    476             new HashMap<String, String>();
    477 
    478     public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
    479         final int orientation = res.getConfiguration().orientation;
    480         final String key = overrideResId + "-" + orientation;
    481         if (!sDeviceOverrideValueMap.containsKey(key)) {
    482             String overrideValue = defValue;
    483             for (final String element : res.getStringArray(overrideResId)) {
    484                 if (element.startsWith(HARDWARE_PREFIX)) {
    485                     overrideValue = element.substring(HARDWARE_PREFIX.length());
    486                     break;
    487                 }
    488             }
    489             sDeviceOverrideValueMap.put(key, overrideValue);
    490         }
    491         return sDeviceOverrideValueMap.get(key);
    492     }
    493 
    494     private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = new HashMap<String, Long>();
    495     private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
    496     public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
    497         if (TextUtils.isEmpty(str)) {
    498             return EMPTY_LT_HASH_MAP;
    499         }
    500         final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
    501         final int N = ss.length;
    502         if (N < 2 || N % 2 != 0) {
    503             return EMPTY_LT_HASH_MAP;
    504         }
    505         final HashMap<String, Long> retval = new HashMap<String, Long>();
    506         for (int i = 0; i < N / 2; ++i) {
    507             final String localeStr = ss[i * 2];
    508             final long time = Long.valueOf(ss[i * 2 + 1]);
    509             retval.put(localeStr, time);
    510         }
    511         return retval;
    512     }
    513 
    514     public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
    515         if (map == null || map.isEmpty()) {
    516             return "";
    517         }
    518         final StringBuilder builder = new StringBuilder();
    519         for (String localeStr : map.keySet()) {
    520             if (builder.length() > 0) {
    521                 builder.append(LOCALE_AND_TIME_STR_SEPARATER);
    522             }
    523             final Long time = map.get(localeStr);
    524             builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
    525             builder.append(String.valueOf(time));
    526         }
    527         return builder.toString();
    528     }
    529 }
    530