• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
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      */
cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning)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 
getInstance()80         public static GCUtils getInstance() {
81             return sInstance;
82         }
83 
reset()84         public void reset() {
85             mGCTryCount = 0;
86         }
87 
tryGCOrWait(String metaData, Throwable t)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 
RingCharBuffer()121         private RingCharBuffer() {
122             // Intentional empty constructor for singleton.
123         }
getInstance()124         public static RingCharBuffer getInstance() {
125             return sRingCharBuffer;
126         }
init(InputMethodService context, boolean enabled, boolean usabilityStudy)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         }
normalize(int in)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
push(char c, int x, int y)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         }
pop()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         }
getBackwardNthChar(int n)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         }
getPreviousX(char c, int back)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         }
getPreviousY(char c, int back)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         }
getLastWord(int ignoreCharCount)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         }
reset()203         public void reset() {
204             mLength = 0;
205         }
206     }
207 
208     // Get the current stack trace
getStackTrace()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 
UsabilityStudyLogUtils()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 
getInstance()248         public static UsabilityStudyLogUtils getInstance() {
249             return OnDemandInitializationHolder.sInstance;
250         }
251 
init(InputMethodService ims)252         public void init(InputMethodService ims) {
253             mIms = ims;
254             mDirectory = ims.getFilesDir();
255         }
256 
createLogFileIfNotExist()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 
writeBackSpace(int x, int y)268         public static void writeBackSpace(int x, int y) {
269             UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
270         }
271 
writeChar(char c, int x, int y)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 
write(final String log)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 
getBufferedLogs()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 
emailResearcherLogsAll()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 
printAll()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 
clearAll()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 
getBufferedReader()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 
getPrintWriter( File dir, String filename, boolean renew)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 
getDipScale(Context context)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 */
dipToPixel(float scale, int dip)436     public static int dipToPixel(float scale, int dip) {
437         return (int) (dip * scale + 0.5);
438     }
439 
440     public static class Stats {
onNonSeparator(final char code, final int x, final int y)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 
onSeparator(final int code, final int x, final int y)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 
onAutoCorrection(final String typedWord, final String correctedWord, final int separatorCode)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 
onAutoCorrectionCancellation()460         public static void onAutoCorrectionCancellation() {
461             LatinImeLogger.logOnAutoCorrectionCancelled();
462         }
463     }
464 
getDebugInfo(final SuggestedWords suggestions, final int pos)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 
getDeviceOverrideValue(Resources res, int overrideResId, String defValue)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 = ",";
localeAndTimeStrToHashMap(String str)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 
localeAndTimeHashMapToStr(HashMap<String, Long> map)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