am 262b1e75: Merge "Remove researcher logger"

* commit '262b1e75182ecd34e7488c6ac98341c45dc9f24d':
  Remove researcher logger
main
Tadashi G. Takaoka 2014-05-29 07:32:52 +00:00 committed by Android Git Automerger
commit 08af47cb03
104 changed files with 4 additions and 6079 deletions

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.inputmethod.research.FeedbackLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/research_feedback_layout"
>
<fragment
android:id="@+id/research_feedback_fragment"
android:name="com.android.inputmethod.research.FeedbackFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.android.inputmethod.research.FeedbackLayout>

View File

@ -1,115 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Adapted from frameworks/base/core/res/res/layout/alert_dialog_holo.xml. We
want a dialog, but it must be its own activity so we can launch the soft
keyboard on it. A regular dialog will not work since it would be launched from
the IME. -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dip"
android:layout_marginEnd="8dip"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View android:layout_width="match_parent"
android:layout_height="2dip"
android:visibility="gone"
android:background="@android:color/holo_blue_light" />
<TextView
style="?android:attr/windowTitleStyle"
android:singleLine="true"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="64dip"
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:gravity="center_vertical|left"
android:text="@string/research_feedback_dialog_title" />
<View
android:layout_width="match_parent"
android:layout_height="2dip"
android:background="@android:color/holo_blue_light" />
</LinearLayout>
<EditText
android:id="@+id/research_feedback_contents"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_gravity="fill_horizontal|center_vertical"
android:layout_marginLeft="8dip"
android:layout_marginRight="8dip"
android:layout_marginBottom="8dip"
android:layout_marginTop="8dip"
android:minLines="2"
android:scrollbars="vertical"
android:hint="@string/research_feedback_hint"
android:inputType="textMultiLine|textCapSentences">
<requestFocus />
</EditText>
<CheckBox
android:id="@+id/research_feedback_include_account_name"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:layout_marginBottom="8dip"
android:checked="false"
android:text="@string/research_feedback_include_account_name_label" />
<CheckBox
android:id="@+id/research_feedback_include_recording_checkbox"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:layout_marginBottom="8dip"
android:checked="false"
android:text="@string/research_feedback_include_recording_label" />
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layoutDirection="locale"
android:measureWithLargestChild="true">
<Button
android:id="@+id/research_feedback_cancel_button"
android:layout_width="wrap_content"
android:layout_gravity="left"
android:layout_weight="1"
android:maxLines="2"
style="?android:attr/buttonBarButtonStyle"
android:textSize="14sp"
android:text="@string/research_feedback_cancel"
android:layout_height="wrap_content" />
<Button
android:id="@+id/research_feedback_send_button"
android:layout_width="wrap_content"
android:layout_gravity="right"
android:layout_weight="1"
android:maxLines="2"
style="?android:attr/buttonBarButtonStyle"
android:textSize="14sp"
android:text="@string/research_feedback_send"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<EditText
android:id="@+id/research_feedback_contents"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_gravity="fill_horizontal|center_vertical"
android:layout_marginLeft="8dip"
android:layout_marginRight="8dip"
android:layout_marginBottom="8dip"
android:layout_marginTop="8dip"
android:lines="2"
android:hint="@string/research_feedback_hint"
android:inputType="textMultiLine"
android:imeOptions="flagNoFullscreen"
android:focusable="true"
>
<requestFocus />
</EditText>
<CheckBox
android:id="@+id/research_feedback_include_history"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginBottom="8dip"
android:checked="true"
android:text="@string/research_feedback_include_history_label"
/>
</LinearLayout>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropsies"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Navorsing-loglêerbevele"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Soek kontakname op"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Speltoetser gebruik inskrywings uit jou kontaklys"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibreer met sleuteldruk"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"ግቤት አማራጮች"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"የጥናት የምዝግብ ማስታወሻ ትዕዛዞች"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"የእውቅያ ስሞችን ተመልከት"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"ፊደል አራሚ ከእውቅያ ዝርዝርህ የገቡትን ይጠቀማል"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"በቁልፍመጫንጊዜ አንዝር"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"خيارات الإرسال"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"أوامر سجلات البحث"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"بحث في أسماء جهات الاتصال"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"يستخدم المدقق الإملائي إدخالات من قائمة جهات الاتصال"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"اهتزاز عند ضغط مفتاح"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Daxiletmə seçimləri"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Araşdırma Jurnalı Əmrləri"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontakt adlarına baxın"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Orfoqrafik yoxlanış kontakt siyahınızdakı qeydlərdən istifadə edir"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrasiyalı klikləmə"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Опции за въвеждане"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Команди за рег. файл за проучвания"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Търсене на имена"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"За проверка на правописа се ползват записи от списъка с контакти"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Да вибрира при натискане на клавиш"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opcions d\'entrada"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Recerca d\'ordres de reg."</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cerca noms de contactes"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"El corrector ortogràfic utilitza entrades de la llista de contactes"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibra en prémer tecles"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávání textu a dat"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Příkazy vývoj. protokolu"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Vyhledat kontakty"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kontrola pravopisu používá záznamy z vašeho seznamu kontaktů."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Při stisku klávesy vibrovat"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Indstillinger for input"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Forskningslogkommandoer"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Anvend kontaktnavne"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Stavekontrollen bruger ord fra dine kontaktpersondata"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibration ved tastetryk"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Eingabeoptionen"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Forschungsprotokollbefehle"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontaktnamen prüfen"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Rechtschreibprüfung kann Einträge aus meiner Kontaktliste verwenden"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Bei Tastendruck vibrieren"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Επιλογές εισόδου"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Έρευνα εντολών καταγραφής"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Αναζήτηση ονομάτων επαφών"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Ο ορθογρ. έλεγχος χρησιμοπ. καταχωρίσεις από τη λίστα επαφών σας"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Δόνηση κατά το πάτημα πλήκτρων"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Input options"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Research Log Commands"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Look up contact names"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Spell checker uses entries from your contact list"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrate on keypress"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Input options"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Research Log Commands"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Look up contact names"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Spell checker uses entries from your contact list"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrate on keypress"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opciones de entrada"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Comandos registro invest."</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nombres contactos"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"El corrector ortográfico usa entradas de tu lista de contactos."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar al pulsar teclas"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opciones entrada texto"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Comandos registro investigación"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Nombres de contactos"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Añadir nombres de tu lista de contactos al corrector"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar al pulsar tecla"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Sisestusvalikud"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Uuringulogi käsud"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontakti nimede kontroll."</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Õigekirjakontroll kasutab teie kontaktisikute loendi sissekandeid"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibreeri klahvivajutusel"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"گزینه‌های ورودی"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"فرمان‌های گزارش‌گیری پژوهش"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"جستجوی نام مخاطبین"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"غلط‌گیر املا از ورودی‌های لیست مخاطبین شما استفاده می‌کند"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"لرزش با فشار کلید"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Syöttövalinnat"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Tutkimuslokin komennot"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Hae yht.tietojen nimiä"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Oikeinkirjoituksen tarkistus käyttää yhteystietojasi."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Käytä värinää näppäimiä painettaessa"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Options de saisie"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Commandes journaux rech."</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Rechercher noms contacts"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Correcteur orthographique utilise entrées de liste de contacts."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer à chaque touche"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Options de saisie"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Commandes journaux rech."</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Rechercher noms contacts"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Correcteur orthographique utilise entrées de liste de contacts."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer à chaque touche"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"इनपुट विकल्‍प"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"लॉग आदेशों का शोध करें"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"संपर्क नामों को खोजें"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"वर्तनी परीक्षक आपकी संपर्क सूची की प्रविष्टियों का उपयोग करता है"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"कुंजी दबाने पर कंपन करता है"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opcije ulaza"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Istraživanje naredbi dnevnika"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Potražite imena kontakata"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Provjera pravopisa upotrebljava unose iz vašeg popisa kontakata"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibracija pri pritisku na tipku"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Beviteli beállítások"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Naplózási parancsok"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Névjegyek keresése"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"A helyesírás-ellenőrző használja a névjegyek bejegyzéseit"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Rezgés gombnyomásra"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Ներածման ընտրանքներ"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Հետազոտական գրառումների հրամաններ"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Փնտրել կոնտակտային անուններ"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Տառասխալների ուղղիչն օգտագործում է ձեր կոնտակտների ցանկի տվյալները"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Թրթռալ սեղմման ժամանակ"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opsi masukan"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Riset Perintah Log"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kontak"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Pemeriksa ejaan menggunakan entri dari daftar kontak Anda"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar jika tombol ditekan"</string>

View File

@ -22,7 +22,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- no translation found for english_ime_input_options (3909945612939668554) -->
<skip />
<!-- no translation found for english_ime_research_log (8492602295696577851) -->
<skip />
<!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
<skip />

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opzioni inserimento"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Ricerca comandi di log"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cerca in nomi contatti"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"La funzione di controllo ortografico usa voci dell\'elenco contatti"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrazione tasti"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"אפשרויות קלט"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"פקודות יומן מחקר"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"חפש שמות של אנשי קשר"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"בודק האיות משתמש בערכים מרשימת אנשי הקשר שלך"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"רטט בלחיצה על מקשים"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"入力オプション"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"ログコマンドの検索"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"連絡先名の検索"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"スペルチェッカーでは連絡先リストのエントリを使用します"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"キー操作バイブ"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"შეყვანის მეთოდები"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"კვლევის აღრიცხვის ბრძანებები"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"კონტაქტებში ძებნა"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"კონტაქტების სიის გამოყენება მართლწერის შემოწმებისას"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"ვიბრაცია კლავიშზე დაჭერისას"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Енгізу опциялары"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Журнал пәрмендерін зерттеу"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Контакт аттарын іздеу"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Емлені тексеру құралы контактілер тізімінің жазбаларын пайдаланады"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Пернені басқан кездегі діріл"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"ជម្រើស​​បញ្ចូល"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"ពាក្យ​បញ្ជា​កំណត់​ហេតុ​​ការ​ស្រាវជ្រាវ"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"រក​មើល​ឈ្មោះ​ទំនាក់ទំនង"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"កម្មវិធី​ពិនិត្យ​អក្ខរាវិរុទ្ធ​ប្រើ​ធាតុ​ពី​​ក្នុង​បញ្ជី​ទំនាក់ទំនង​របស់​អ្នក"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"ញ័រ​នៅ​ពេល​ចុច​គ្រាប់ចុច"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"입력 옵션"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"로그 명령 탐색"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"연락처 이름 조회"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"맞춤법 검사기가 주소록의 항목을 사용합니다."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"키를 누를 때 진동 발생"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"ຕົວເລືອກການປ້ອນຂໍ້ມູນ"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Research Log Commands"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"ເບິ່ງທີ່ຊື່ຂອງລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"ໂຕຊ່ວຍສະກົດໃຊ້ຂໍ້ມູນຈາກລາຍການຂອງລາຍຊື່ຜູ່ຕິດຕໍ່ຂອງທ່ານ"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"ສັ່ນເຕືອນເມື່ອພິມ"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Įvesties parinktys"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Tyrinėti žurnalo komandas"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontaktų vardų paieška"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Rašybos tikrinimo progr. naudoja įrašus, esančius kontaktų sąraše"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibruoti, kai paspaudžiami klavišai"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Ievades opcijas"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Izpētes žurnāla komandas"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Meklēt kontaktp. vārdus"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Pareizrakst. pārbaudītājs lieto ierakstus no kontaktp. saraksta."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrēt, nospiežot taustiņu"</string>

View File

@ -22,7 +22,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- no translation found for english_ime_input_options (3909945612939668554) -->
<skip />
<!-- no translation found for english_ime_research_log (8492602295696577851) -->
<skip />
<!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
<skip />

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Оруулах сонголтууд"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Судалгааны протоколын командууд"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Харилцагчийн нэр хайх"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Алдаа шалгагч нь таны харилцагчдын жагсаалтаас ашиглана"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Товч дарахад чичрэх"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Pilihan input"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Arahan Log Penyelidikan"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kenalan"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Penyemak ejaan menggunakan entri dari senarai kenalan anda"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar pada tekanan kekunci"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Inndataalternativer"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Kommandoer for undersøkelseslogging"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Slå opp kontaktnavn"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Stavekontrollen bruker oppføringer fra kontaktlisten din"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer ved tastetrykk"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"इनपुट विकल्पहरू"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"लग निर्देशनहरू शोध गर्नुहोस्"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"सम्पर्क नामहरू हेर्नुहोस्"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"तपाईँको सम्पर्क सूचीबाट हिज्जे परीक्षकले प्रविष्टिहरूको प्रयोग गर्छ"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"कुञ्जी थिच्दा भाइब्रेट"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropties"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Opdrachten in onderzoekslogbestand"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Contactnamen opzoeken"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"De spellingcontrole gebruikt items uit uw contactenlijst"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Trillen bij toetsaanslag"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opcje wprowadzania"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Polecenia dziennika badań"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Przeszukaj kontakty"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Sprawdzanie pisowni bierze pod uwagę wpisy z listy kontaktów."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Wibracja przy naciśnięciu"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opções de introdução"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Comandos de Reg. Invest."</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Procurar nomes de contac."</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"O corretor ortográfico utiliza entradas da sua lista de contactos"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar ao primir as teclas"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opções de entrada"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Pesq. comandos de reg."</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nomes de contatos"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"O corretor ortográfico usa entradas de sua lista de contatos"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar ao tocar a tecla"</string>

View File

@ -22,7 +22,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- no translation found for english_ime_input_options (3909945612939668554) -->
<skip />
<!-- no translation found for english_ime_research_log (8492602295696577851) -->
<skip />
<!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
<skip />

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Opţiuni de introducere text"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Comenzi jurnal cercetare"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Verificare nume în agendă"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Verificatorul ortografic utilizează intrări din lista de contacte"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrare la apăsarea tastei"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Настройки"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Все команды"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Поиск контактов"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Обращаться к списку контактов при проверке правописания"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Виброотклик клавиш"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávania textu a údajov"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Príkazy denníka výskumu"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Vyhľadať kontakty"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kontrola pravopisu používa záznamy z vášho zoznamu kontaktov"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Pri stlačení klávesu vibrovať"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti vnosa"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Ukazi za dnevnik raziskav"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Iskanje imen stikov"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Črkovalnik uporablja vnose s seznama stikov"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibriranje ob pritisku tipke"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Опције уноса"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Команде евиденције истраживања"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Потражи имена контаката"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Контролор правописа користи уносе са листе контаката"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Вибрирај на притисак тастера"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Inmatningsalternativ"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Loggkommandon"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Sök namn på kontakter"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"I stavningskontrollen används poster från kontaktlistan"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrera vid tangenttryck"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Chaguo za uingizaji"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Amri za Kumbukumbu za Utafiti"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Angalia majina ya unaowasiliana nao"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kikagua tahajia hutumia majina yaliyoingizwa katika orodha yako ya anwani"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Tetema unabofya kitufe"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"ตัวเลือกการป้อนข้อมูล"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"คำสั่งบันทึกการวิจัย"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"ค้นหารายชื่อติดต่อ"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"เครื่องมือตรวจการสะกดใช้รายการจากรายชื่อติดต่อของคุณ"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"สั่นเมื่อกดปุ่ม"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Mga pagpipilian sa input"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Cmmnd sa Log ng Pnnliksik"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Maghanap pangalan contact"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Gumagamit ang Spell Checker ng entries mula sa iyong contact list."</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Mag-vibrate sa keypress"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Giriş seçenekleri"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Araştırma Günlüğü Komutları"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kişi adlarını denetle"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Yazım denetleyici, kişi listenizdeki girişleri kullanır"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Tuşa basıldığında titret"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Парам. введення"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Команди журналу дослідж."</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Шукати імена контактів"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Програма перевірки правопису використ. записи зі списку контактів"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Вібр. при натисканні клавіш"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Tùy chọn nhập"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Lệnh ghi nhật ký cho nghiên cứu"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Tra cứu tên liên hệ"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Trình kiểm tra chính tả sử dụng các mục nhập từ danh sách liên hệ của bạn"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Rung khi nhấn phím"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"输入选项"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"研究记录命令"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查找联系人姓名"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼写检查工具会使用您的联系人列表中的条目"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"按键振动"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"研究記錄指令"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查找聯絡人姓名"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼字檢查程式使用您的聯絡人名單中的各項記錄"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"按鍵時震動"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"研究紀錄指令"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查詢聯絡人姓名"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼字檢查程式使用您的聯絡人清單項目"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"按鍵時震動"</string>

View File

@ -21,7 +21,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="english_ime_input_options" msgid="3909945612939668554">"Okukhethwa kukho kokungenayo"</string>
<string name="english_ime_research_log" msgid="8492602295696577851">"Imiyalo yefayela lokungena lokucwaninga"</string>
<string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Bheka amagama woxhumana nabo"</string>
<string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Isihloli sokupela sisebenzisa okungenayo kusuka kuhlu lalabo oxhumana nabo"</string>
<string name="vibrate_on_keypress" msgid="5258079494276955460">"Dlidlizelisa ngokucindezela inkinobho"</string>

View File

@ -137,6 +137,4 @@
<item>tr:AsciiCapable,SupportTouchPositionCorrection,EmojiCapable</item>
<item>qwerty</item>
</string-array>
<string name="settings_warning_researcher_mode">Attention! You are using the special keyboard for research purposes.</string>
</resources>

View File

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2012, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Contents of note explaining what data is collected and how. -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_splash_content" translatable="false"></string>
<!-- Account type allowed for inclusion in user-invoked feedback logs [CHAR LIMIT=38] -->
<string name="research_account_type" translatable="false"></string>
<!-- Account domain allowed for inclusion in user-invoked feedback logs [CHAR LIMIT=38] -->
<string name="research_allowed_account_domain" translatable="false"></string>
<!-- Menu option that lets user send feedback for research purposes about the IME [CHAR LIMIT=38] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_menu_option" translatable="false">Send feedback</string>
<!-- Title of dialog box that lets user send feedback for research purposes about the IME [CHAR LIMIT=38] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_dialog_title" translatable="false">Send feedback</string>
<!-- Hint to user about the text entry field where they should enter research feedback [CHAR LIMIT=40] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_hint" translatable="false">Enter your feedback here.</string>
<!-- Message informing the user that the feedback string must not be empty [CHAR LIMIT=100] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_empty_feedback_error_message" translatable="false">The feedback field must not be empty.</string>
</resources>

View File

@ -21,9 +21,6 @@
<!-- Title for Latin keyboard input options dialog [CHAR LIMIT=25] -->
<string name="english_ime_input_options">Input options</string>
<!-- Title for Latin keyboard research log dialog, which contains special commands for users that contribute data for research. [CHAR LIMIT=33] -->
<string name="english_ime_research_log">Research Log Commands</string>
<!-- Title for the spell checker option to turn on/off contact names lookup [CHAR LIMIT=25] -->
<string name="use_contacts_for_spellchecking_option_title">Look up contact names</string>
@ -166,68 +163,8 @@
<!-- Title for input language selection screen -->
<string name="language_selection_title">Input languages</string>
<!-- Title for dialog option to let users cancel logging and delete log for this session [CHAR LIMIT=35] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_do_not_log_this_session" translatable="false">Suspend logging</string>
<!-- Title for dialog option to let users reenable logging [CHAR LIMIT=35] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_enable_session_logging" translatable="false">Enable logging</string>
<!-- Toast notification that the system is processing the request to delete the log for this session [CHAR LIMIT=35] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_notify_session_log_deleting" translatable="false">Deleting session log</string>
<!-- Toast notification that the system has successfully deleted the log for this session [CHAR LIMIT=35] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_notify_logging_suspended" translatable="false">Logging temporarily suspended. To disable permanently, go to Android Keyboard Settings</string>
<!-- Toast notification that the system has failed to delete the log for this session [CHAR LIMIT=35] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_notify_session_log_not_deleted" translatable="false">Session log NOT deleted</string>
<!-- Toast notification that the system is enabling logging [CHAR LIMIT=35] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_notify_session_logging_enabled" translatable="false">Session logging enabled</string>
<!-- Text for checkbox option to include user data in feedback for research purposes [CHAR LIMIT=50] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_include_history_label" translatable="false">Include session history</string>
<!-- Text for checkbox option to include user account name in feedback for research purposes [CHAR LIMIT=50] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_include_account_name_label" translatable="false">Include account name</string>
<!-- Text for checkbox option to include a recording in feedback for research purposes [CHAR LIMIT=50] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_include_recording_label" translatable="false">Include recorded demonstration</string>
<!-- Dialog button choice to send research feedback [CHAR LIMIT=35] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_send" translatable="false">Send</string>
<!-- Dialog button choice to cancel sending research feedback [CHAR LIMIT=35] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_cancel" translatable="false">Cancel</string>
<!-- Temporary notification to provide user with instructions about stopping a recording
- operation[CHAR LIMIT=100] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_demonstration_instructions" translatable="false">Please demonstrate the issue you are writing about.\n\nWhen finished, select the \"Bug?\" button again."</string>
<!-- Title of a preference to send feedback. [CHAR LIMIT=30]-->
<string name="send_feedback">Send feedback</string>
<!-- Temporary notification of recording failure [CHAR LIMIT=100] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_recording_failure" translatable="false">Recording cancelled due to timeout</string>
<!-- Toast notification to ask user to quit the research feedback dialog to perform this operation [CHAR LIMIT=100] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_please_exit_feedback_form" translatable="false">Please exit the feedback dialog to access the research log menu</string>
<!-- Title of dialog shown at start informing users about contributing research usage data-->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_splash_title" translatable="false">Warning</string>
<!-- Toast message informing users that logging has been disabled -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_logging_disabled" translatable="false">Logging Disabled</string>
<!-- Name for the research uploading service to be displayed to users. [CHAR LIMIT=50] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_log_uploader_name" translatable="false">Research Uploader Service</string>
<!-- Name for the research replaying service to be displayed to users. [CHAR LIMIT=50] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_log_replayer_name" translatable="false">Research Replayer Service</string>
<!-- Preference for input language selection -->
<string name="select_language">Input languages</string>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2012, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<resources>
<string name="research_logger_upload_url" translatable="false"></string>
</resources>

View File

@ -37,9 +37,7 @@ import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.utils.TypefaceUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.HashSet;
@ -317,13 +315,6 @@ public class KeyboardView extends View {
}
}
// Research Logging (Development Only Diagnostics) indicator.
// TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
// and remove this call.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height);
}
mInvalidatedKeys.clear();
mInvalidateAllKeys = false;
}

View File

@ -55,13 +55,11 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.DebugSettings;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
import com.android.inputmethod.latin.utils.TypefaceUtils;
import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.WeakHashMap;
@ -387,10 +385,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio;
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final int orientation = getContext().getResources().getConfiguration().orientation;
ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
}
mAccessibilityDelegate.setKeyboard(keyboard);
}
@ -552,24 +546,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
protected void onAttachedToWindow() {
super.onAttachedToWindow();
installPreviewPlacerView();
// Notify the ResearchLogger (development only diagnostics) that the keyboard view has
// been attached. This is needed to properly show the splash screen, which requires that
// the window token of the KeyboardView be non-null.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mDrawingPreviewPlacerView.removeAllViews();
// Notify the ResearchLogger (development only diagnostics) that the keyboard view has
// been detached. This is needed to invalidate the reference of {@link MainKeyboardView}
// to null.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
}
}
private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
@ -605,9 +587,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (key == null) {
return;
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.mainKeyboardView_onLongPress();
}
final KeyboardActionListener listener = mKeyboardActionListener;
if (key.hasNoPanelAutoMoreKey()) {
final int moreKeyCode = key.getMoreKeys()[0].mCode;
@ -723,10 +702,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (LatinImeLogger.sUsabilityStudy) {
UsabilityStudyLogUtils.writeMotionEvent(me);
}
// Currently the same "move" event is being logged twice.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.mainKeyboardView_processMotionEvent(me);
}
final int index = me.getActionIndex();
final int id = me.getPointerId(index);

View File

@ -35,11 +35,9 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@ -335,10 +333,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
output, ignoreModifierKey ? " ignoreModifier" : "",
altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled"));
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey,
altersCode, code);
}
if (ignoreModifierKey) {
return;
}
@ -373,10 +367,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "",
key.isEnabled() ? "": " disabled"));
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding,
ignoreModifierKey);
}
if (ignoreModifierKey) {
return;
}
@ -396,9 +386,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onCancelInput", mPointerId));
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.pointerTracker_callListenerOnCancelInput();
}
sListener.onCancelInput();
}
@ -702,9 +689,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
Log.w(TAG, String.format("[%d] onDownEvent:"
+ " ignore potential noise: time=%d distance=%d",
mPointerId, deltaT, distance));
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance);
}
cancelTrackingForAction();
return;
}
@ -876,10 +860,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
lastX, lastY, Constants.printableCode(oldKey.getCode()),
x, y, Constants.printableCode(key.getCode())));
}
// TODO: This should be moved to outside of this nested if-clause?
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
}
onUpEventInternal(x, y, eventTime);
onDownEventInternal(x, y, eventTime);
}

View File

@ -28,7 +28,6 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@ -38,7 +37,6 @@ import android.net.ConnectivityManager;
import android.os.Debug;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
@ -90,7 +88,6 @@ import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@ -494,11 +491,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
loadSettings();
resetSuggest();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
ResearchLogger.getInstance().initDictionary(mDictionaryFacilitator);
}
// Register to receive ringer mode change and network state change.
// Also receive installation and removal of a dictionary pack.
final IntentFilter filter = new IntentFilter();
@ -631,9 +623,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mDictionaryFacilitator.closeDictionaries();
mSettings.onDestroy();
unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().onDestroy();
}
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
PersonalizationDictionarySessionRegistrar.close(this);
@ -757,10 +746,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
Log.i(TAG, "Starting input. Cursor position = "
+ editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, prefs);
}
if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
@ -905,10 +890,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
// Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
mInputLogic.finishInput();
// Notify ResearchLogger
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput);
}
}
@Override
@ -922,11 +903,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", nss=" + newSelStart + ", nse=" + newSelEnd
+ ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onUpdateSelection(oldSelStart, oldSelEnd,
oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
composingSpanEnd, mInputLogic.mConnection);
}
// If the keyboard is not visible, we don't need to do all the housekeeping work, as it
// will be reset when the keyboard shows up anyway.
@ -1013,9 +989,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (applicationSpecifiedCompletions == null) {
setNeutralSuggestionStrip();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onDisplayCompletions(null);
}
return;
}
@ -1027,9 +1000,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
false /* isObsoleteSuggestions */, false /* isPrediction */);
// When in fullscreen mode, show completions generated by the application forcibly
setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
}
}
private int getAdjustedBackingViewHeight() {

View File

@ -26,14 +26,12 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
import com.android.inputmethod.latin.utils.SpannableStringUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.ResearchLogger;
import java.util.Arrays;
import java.util.regex.Pattern;
@ -174,9 +172,6 @@ public final class RichInputConnection {
}
if (null != mIC && shouldFinishComposition) {
mIC.finishComposingText();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_finishComposingText();
}
}
return true;
}
@ -223,9 +218,6 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.finishComposingText();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_finishComposingText();
}
}
}
@ -363,9 +355,6 @@ public final class RichInputConnection {
}
if (null != mIC) {
mIC.deleteSurroundingText(beforeLength, afterLength);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_deleteSurroundingText(beforeLength, afterLength);
}
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@ -374,9 +363,6 @@ public final class RichInputConnection {
mIC = mParent.getCurrentInputConnection();
if (null != mIC) {
mIC.performEditorAction(actionId);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_performEditorAction(actionId);
}
}
}
@ -429,9 +415,6 @@ public final class RichInputConnection {
}
if (null != mIC) {
mIC.sendKeyEvent(keyEvent);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
}
}
}
@ -469,9 +452,6 @@ public final class RichInputConnection {
// newCursorPosition != 1.
if (null != mIC) {
mIC.setComposingText(text, newCursorPosition);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_setComposingText(text, newCursorPosition);
}
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@ -500,9 +480,6 @@ public final class RichInputConnection {
if (!isIcValid) {
return false;
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_setSelection(start, end);
}
}
return reloadTextCache();
}
@ -530,9 +507,6 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_commitCompletion(completionInfo);
}
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@ -765,9 +739,6 @@ public final class RichInputConnection {
deleteSurroundingText(2, 0);
final String singleSpace = " ";
commitText(singleSpace, 1);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_revertDoubleSpacePeriod();
}
return true;
}
@ -790,9 +761,6 @@ public final class RichInputConnection {
deleteSurroundingText(2, 0);
final String text = " " + textBeforeCursor.subSequence(0, 1);
commitText(text, 1);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_revertSwapPunctuation();
}
return true;
}

View File

@ -21,13 +21,6 @@ public final class ProductionFlag {
// This class is not publicly instantiable.
}
public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS = false;
// When false, USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG suggests that all guarded
// class-private DEBUG flags should be false, and any privacy controls should be enforced.
// USES_DEVELOPMENT_ONLY_DIAGNOSTICS must be false for any production build.
public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false;
public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false;
// When true, enable {@link InputMethodService#onUpdateCursor} callback with

View File

@ -44,7 +44,6 @@ import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
@ -54,7 +53,6 @@ import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
import java.util.TreeSet;
@ -201,19 +199,11 @@ public final class InputLogic {
resetComposingState(true /* alsoResetLastComposedWord */);
}
handler.postUpdateSuggestionStrip();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
&& ResearchLogger.RESEARCH_KEY_OUTPUT_TEXT.equals(rawText)) {
ResearchLogger.getInstance().onResearchKeySelected(mLatinIME);
return;
}
final String text = performSpecificTldProcessingOnTextInput(rawText);
if (SpaceState.PHANTOM == mSpaceState) {
promotePhantomSpace(settingsValues);
}
mConnection.commitText(text, 1);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
}
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.NONE;
@ -244,10 +234,6 @@ public final class InputLogic {
LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
false /* isBatchMode */, suggestedWords.mIsPrediction);
}
return onCodeInput(settingsValues, event, keyboardShiftState, handler);
}
@ -286,11 +272,6 @@ public final class InputLogic {
LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
commitChosenWord(settingsValues, suggestion,
LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
mWordComposer.isBatchMode(), suggestionInfo.mScore,
suggestionInfo.mKindAndFlags, suggestionInfo.mSourceDict.mDictType);
}
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
mLastComposedWord.deactivate();
@ -403,9 +384,6 @@ public final class InputLogic {
final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
SystemClock.uptimeMillis(), mSpaceState,
getActualCapsMode(settingsValues, keyboardShiftMode));
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onCodeInput(code, event.mX, event.mY);
}
if (event.mKeyCode != Constants.CODE_DELETE
|| inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
mDeleteCount = 0;
@ -854,9 +832,6 @@ public final class InputLogic {
if (needsPrecedingSpace) {
promotePhantomSpace(settingsValues);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_handleSeparator(codePoint, mWordComposer.isComposingWord());
}
if (!shouldAvoidSendingCode) {
sendKeyCodePoint(settingsValues, codePoint);
@ -932,10 +907,6 @@ public final class InputLogic {
}
if (mWordComposer.isComposingWord()) {
if (mWordComposer.isBatchMode()) {
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final String word = mWordComposer.getTypedWord();
ResearchLogger.latinIME_handleBackspace_batch(word, 1);
}
final String rejectedSuggestion = mWordComposer.getTypedWord();
mWordComposer.reset();
mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
@ -961,9 +932,6 @@ public final class InputLogic {
// This is triggered on backspace after a key that inputs multiple characters,
// like the smiley key or the .com key.
mConnection.deleteSurroundingText(mEnteredText.length(), 0);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText);
}
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
@ -993,10 +961,6 @@ public final class InputLogic {
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
mConnection.deleteSurroundingText(numCharsDeleted, 0);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
false /* shouldUncommitLogUnit */);
}
} else {
// There is no selection, just delete one character.
if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
@ -1031,10 +995,6 @@ public final class InputLogic {
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_handleBackspace(lengthToDelete,
true /* shouldUncommitLogUnit */);
}
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
@ -1042,10 +1002,6 @@ public final class InputLogic {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
true /* shouldUncommitLogUnit */);
}
}
}
}
@ -1083,9 +1039,6 @@ public final class InputLogic {
mConnection.deleteSurroundingText(2, 0);
final String text = lastTwo.charAt(1) + " ";
mConnection.commitText(text, 1);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
}
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
}
}
@ -1169,10 +1122,6 @@ public final class InputLogic {
final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mSentenceSeparatorAndSpace;
mConnection.commitText(textToInsert, 1);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
false /* isBatchMode */);
}
mWordComposer.discardPreviousWordForSuggestion();
return true;
}
@ -1519,13 +1468,7 @@ public final class InputLogic {
LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_revertCommit(committedWord.toString(),
originallyTypedWord.toString(),
mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
}
// Don't restart suggestion yet. We'll restart if the user deletes the
// separator.
// Don't restart suggestion yet. We'll restart if the user deletes the separator.
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
// We have a separator between the word and the cursor: we should show predictions.
inputTransaction.setRequiresUpdateSuggestions();
@ -1789,9 +1732,6 @@ public final class InputLogic {
*/
// TODO: replace these two parameters with an InputTransaction
private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) {
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_sendKeyCodePoint(codePoint);
}
// TODO: Remove this special handling of digit letters.
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (codePoint >= '0' && codePoint <= '9') {
@ -1823,9 +1763,6 @@ public final class InputLogic {
if (settingsValues.shouldInsertSpacesAutomatically()
&& settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
&& !mConnection.textBeforeCursorLooksLikeURL()) {
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_promotePhantomSpace();
}
sendKeyCodePoint(settingsValues, Constants.CODE_SPACE);
}
}
@ -1867,9 +1804,6 @@ public final class InputLogic {
mConnection.setComposingText(batchInputText, 1);
}
mConnection.endBatchEdit();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
}
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.PHANTOM;
keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues),
@ -1896,9 +1830,6 @@ public final class InputLogic {
if (!mWordComposer.isComposingWord()) return;
final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
}
commitChosenWord(settingsValues, typedWord,
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString);
}
@ -1942,11 +1873,6 @@ public final class InputLogic {
LatinImeLoggerUtils.onAutoCorrection(
typedWord, autoCorrection, separator, mWordComposer);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final SuggestedWords suggestedWords = mSuggestedWords;
ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
separator, mWordComposer.isBatchMode(), suggestedWords);
}
commitChosenWord(settingsValues, autoCorrection,
LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator);
if (!typedWord.equals(autoCorrection)) {

View File

@ -76,7 +76,6 @@ public final class DebugSettings extends PreferenceFragment
final CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE,
LatinImeLogger.getUsabilityStudyMode(prefs)));
checkbox.setSummary(R.string.settings_warning_researcher_mode);
}
final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING);
if (statisticsLoggingPref instanceof CheckBoxPreference) {

View File

@ -41,7 +41,6 @@ import com.android.inputmethod.keyboard.KeyboardTheme;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
@ -152,10 +151,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
miscSettings.removePreference(aboutSettings);
}
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
// The about screen contains items that may be confusing in development-only versions.
miscSettings.removePreference(aboutSettings);
}
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);

View File

@ -47,11 +47,9 @@ import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@ -226,9 +224,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mSuggestedWords = suggestedWords;
mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip(
mSuggestedWords, mSuggestionsStrip, this);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
}
mStripVisibilityGroup.showSuggestionsStrip(isVoiceKeyEnabled());
}

View File

@ -26,7 +26,6 @@ import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragmen
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
import com.android.inputmethod.research.FeedbackFragment;
import java.util.HashSet;
@ -43,7 +42,6 @@ public class FragmentUtils {
sLatinImeFragments.add(UserDictionaryList.class.getName());
sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName());
sLatinImeFragments.add(UserDictionarySettings.class.getName());
sLatinImeFragments.add(FeedbackFragment.class.getName());
}
public static boolean isValidFragment(String fragmentName) {

View File

@ -43,7 +43,6 @@ import java.util.Date;
import java.util.Locale;
public final class UsabilityStudyLogUtils {
// TODO: remove code duplication with ResearchLog class
private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
private static final String FILENAME = "log.txt";
private final Handler mLoggingHandler;
@ -190,7 +189,7 @@ public final class UsabilityStudyLogUtils {
return sb.toString();
}
public void emailResearcherLogsAll() {
public void emailUsabilityStudyLogsAll() {
mLoggingHandler.post(new Runnable() {
@Override
public void run() {
@ -210,7 +209,7 @@ public final class UsabilityStudyLogUtils {
}
mWriter.flush();
final String destPath = Environment.getExternalStorageDirectory()
+ "/research-" + currentDateTimeString + ".log";
+ "/usability-" + currentDateTimeString + ".log";
final File destFile = new File(destPath);
try {
final FileInputStream srcStream = new FileInputStream(mFile);
@ -241,7 +240,7 @@ public final class UsabilityStudyLogUtils {
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
intent.putExtra(Intent.EXTRA_SUBJECT,
"[Research Logs] " + currentDateTimeString);
"[Usability Study Logs] " + currentDateTimeString);
mIms.startActivity(intent);
}
});

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Arrange for the uploading service to be run on regular intervals.
*/
public final class BootBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
UploaderService.cancelAndRescheduleUploadingService(context,
true /* needsRescheduling */);
}
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.app.Activity;
import android.os.Bundle;
import com.android.inputmethod.latin.R;
public class FeedbackActivity extends Activity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.research_feedback_activity);
final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
layout.setActivity(this);
}
@Override
public void onBackPressed() {
ResearchLogger.getInstance().onLeavingSendFeedbackDialog();
super.onBackPressed();
}
}

View File

@ -1,131 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.app.Fragment;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import com.android.inputmethod.latin.R;
public class FeedbackFragment extends Fragment implements OnClickListener {
private static final String TAG = FeedbackFragment.class.getSimpleName();
public static final String KEY_FEEDBACK_STRING = "FeedbackString";
public static final String KEY_INCLUDE_ACCOUNT_NAME = "IncludeAccountName";
public static final String KEY_HAS_USER_RECORDING = "HasRecording";
private EditText mEditText;
private CheckBox mIncludingAccountNameCheckBox;
private CheckBox mIncludingUserRecordingCheckBox;
private Button mSendButton;
private Button mCancelButton;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.research_feedback_fragment_layout, container,
false);
mEditText = (EditText) view.findViewById(R.id.research_feedback_contents);
mEditText.requestFocus();
mIncludingAccountNameCheckBox = (CheckBox) view.findViewById(
R.id.research_feedback_include_account_name);
mIncludingUserRecordingCheckBox = (CheckBox) view.findViewById(
R.id.research_feedback_include_recording_checkbox);
mIncludingUserRecordingCheckBox.setOnClickListener(this);
mSendButton = (Button) view.findViewById(R.id.research_feedback_send_button);
mSendButton.setOnClickListener(this);
mCancelButton = (Button) view.findViewById(R.id.research_feedback_cancel_button);
mCancelButton.setOnClickListener(this);
if (savedInstanceState != null) {
restoreState(savedInstanceState);
} else {
final Bundle bundle = getActivity().getIntent().getExtras();
if (bundle != null) {
restoreState(bundle);
}
}
return view;
}
@Override
public void onClick(final View view) {
final ResearchLogger researchLogger = ResearchLogger.getInstance();
if (view == mIncludingUserRecordingCheckBox) {
if (mIncludingUserRecordingCheckBox.isChecked()) {
final Bundle bundle = new Bundle();
onSaveInstanceState(bundle);
// Let the user make a recording
getActivity().finish();
researchLogger.setFeedbackDialogBundle(bundle);
researchLogger.onLeavingSendFeedbackDialog();
researchLogger.startRecording();
}
} else if (view == mSendButton) {
final Editable editable = mEditText.getText();
final String feedbackContents = editable.toString();
if (TextUtils.isEmpty(feedbackContents)) {
Toast.makeText(getActivity(),
R.string.research_feedback_empty_feedback_error_message,
Toast.LENGTH_LONG).show();
} else {
final boolean isIncludingAccountName = mIncludingAccountNameCheckBox.isChecked();
researchLogger.sendFeedback(feedbackContents, false /* isIncludingHistory */,
isIncludingAccountName, mIncludingUserRecordingCheckBox.isChecked());
getActivity().finish();
researchLogger.setFeedbackDialogBundle(null);
researchLogger.onLeavingSendFeedbackDialog();
}
} else if (view == mCancelButton) {
Log.d(TAG, "Finishing");
getActivity().finish();
researchLogger.setFeedbackDialogBundle(null);
researchLogger.onLeavingSendFeedbackDialog();
} else {
Log.e(TAG, "Unknown view passed to FeedbackFragment.onClick()");
}
}
@Override
public void onSaveInstanceState(final Bundle bundle) {
final String savedFeedbackString = mEditText.getText().toString();
bundle.putString(KEY_FEEDBACK_STRING, savedFeedbackString);
bundle.putBoolean(KEY_INCLUDE_ACCOUNT_NAME, mIncludingAccountNameCheckBox.isChecked());
bundle.putBoolean(KEY_HAS_USER_RECORDING, mIncludingUserRecordingCheckBox.isChecked());
}
private void restoreState(final Bundle bundle) {
mEditText.setText(bundle.getString(KEY_FEEDBACK_STRING));
mIncludingAccountNameCheckBox.setChecked(bundle.getBoolean(KEY_INCLUDE_ACCOUNT_NAME));
mIncludingUserRecordingCheckBox.setChecked(bundle.getBoolean(KEY_HAS_USER_RECORDING));
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.LinearLayout;
public class FeedbackLayout extends LinearLayout {
private Activity mActivity;
public FeedbackLayout(Context context) {
super(context);
}
public FeedbackLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FeedbackLayout(Context context, AttributeSet attrs, int defstyle) {
super(context, attrs, defstyle);
}
public void setActivity(Activity activity) {
mActivity = activity;
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
state.startTracking(event, this);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP
&& !event.isCanceled() && state.isTracking(event)) {
mActivity.onBackPressed();
return true;
}
}
}
return super.dispatchKeyEventPreIme(event);
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.content.Context;
import java.io.File;
public class FeedbackLog extends ResearchLog {
public FeedbackLog(final File outputFile, final Context context) {
super(outputFile, context);
}
@Override
public boolean isFeedbackLog() {
return true;
}
}

View File

@ -1,174 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* A buffer that holds a fixed number of LogUnits.
*
* LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are
* actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches
* capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to
* stay under the capacity limit.
*
* This variant of a LogBuffer has a limited memory footprint because of its limited size. This
* makes it useful, for example, for recording a window of the user's most recent actions in case
* they want to report an observed error that they do not know how to reproduce.
*/
public class FixedLogBuffer extends LogBuffer {
/* package for test */ int mWordCapacity;
// The number of members of mLogUnits that are actual words.
private int mNumActualWords;
/**
* Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and
* unlimited number of non-word LogUnits), and that outputs its result to a researchLog.
*
* @param wordCapacity maximum number of words
*/
public FixedLogBuffer(final int wordCapacity) {
super();
if (wordCapacity <= 0) {
throw new IllegalArgumentException("wordCapacity must be 1 or greater.");
}
mWordCapacity = wordCapacity;
mNumActualWords = 0;
}
/**
* Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's
* (oldest first) if word capacity is reached.
*/
@Override
public void shiftIn(final LogUnit newLogUnit) {
if (!newLogUnit.hasOneOrMoreWords()) {
// This LogUnit doesn't contain any word, so it doesn't count toward the word-limit.
super.shiftIn(newLogUnit);
return;
}
final int numWordsIncoming = newLogUnit.getNumWords();
if (mNumActualWords >= mWordCapacity) {
// Give subclass a chance to handle the buffer full condition by shifting out logUnits.
// TODO: Tell onBufferFull() how much space it needs to make to avoid forced eviction.
onBufferFull();
// If still full, evict.
if (mNumActualWords >= mWordCapacity) {
shiftOutWords(numWordsIncoming);
}
}
super.shiftIn(newLogUnit);
mNumActualWords += numWordsIncoming;
}
@Override
public LogUnit unshiftIn() {
final LogUnit logUnit = super.unshiftIn();
if (logUnit != null && logUnit.hasOneOrMoreWords()) {
mNumActualWords -= logUnit.getNumWords();
}
return logUnit;
}
public int getNumWords() {
return mNumActualWords;
}
/**
* Removes all LogUnits from the buffer without calling onShiftOut().
*/
@Override
public void clear() {
super.clear();
mNumActualWords = 0;
}
/**
* Called when the buffer has just shifted in one more word than its maximum, and its about to
* shift out LogUnits to bring it back down to the maximum.
*
* Base class does nothing; subclasses may override if they want to record non-privacy sensitive
* events that fall off the end.
*/
protected void onBufferFull() {
}
@Override
public LogUnit shiftOut() {
final LogUnit logUnit = super.shiftOut();
if (logUnit != null && logUnit.hasOneOrMoreWords()) {
mNumActualWords -= logUnit.getNumWords();
}
return logUnit;
}
/**
* Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed.
*
* If there are less than {@code numWords} in the buffer, shifts out all {@code LogUnit}s.
*
* @param numWords the minimum number of words in {@link LogUnit}s to shift out
* @return the number of actual words LogUnit}s shifted out
*/
protected int shiftOutWords(final int numWords) {
int numWordsShiftedOut = 0;
do {
final LogUnit logUnit = shiftOut();
if (logUnit == null) break;
numWordsShiftedOut += logUnit.getNumWords();
} while (numWordsShiftedOut < numWords);
return numWordsShiftedOut;
}
public void shiftOutAll() {
final LinkedList<LogUnit> logUnits = getLogUnits();
while (!logUnits.isEmpty()) {
shiftOut();
}
mNumActualWords = 0;
}
/**
* Returns a list of {@link LogUnit}s at the front of the buffer that have words associated with
* them.
*
* There will be no more than {@code n} words in the returned list. So if 2 words are
* requested, and the first LogUnit has 3 words, it is not returned. If 2 words are requested,
* and the first LogUnit has only 1 word, and the next LogUnit 2 words, only the first LogUnit
* is returned. If the first LogUnit has no words associated with it, and the second LogUnit
* has three words, then only the first LogUnit (which has no associated words) is returned. If
* there are not enough LogUnits in the buffer to meet the word requirement, then all LogUnits
* will be returned.
*
* @param n The maximum number of {@link LogUnit}s with words to return.
* @return The list of the {@link LogUnit}s containing the first n words
*/
public ArrayList<LogUnit> peekAtFirstNWords(int n) {
final LinkedList<LogUnit> logUnits = getLogUnits();
// Allocate space for n*2 logUnits. There will be at least n, one for each word, and
// there may be additional for punctuation, between-word commands, etc. This should be
// enough that reallocation won't be necessary.
final ArrayList<LogUnit> resultList = new ArrayList<>(n * 2);
for (final LogUnit logUnit : logUnits) {
n -= logUnit.getNumWords();
if (n < 0) break;
resultList.add(logUnit);
}
return resultList;
}
}

View File

@ -1,162 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.content.SharedPreferences;
import android.util.JsonWriter;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.io.IOException;
import java.util.Map;
/**
* Routines for mapping classes and variables to JSON representations for logging.
*/
/* package */ class JsonUtils {
private JsonUtils() {
// This utility class is not publicly instantiable.
}
/* package */ static void writeJson(final CompletionInfo[] ci, final JsonWriter jsonWriter)
throws IOException {
jsonWriter.beginArray();
for (int j = 0; j < ci.length; j++) {
jsonWriter.value(ci[j].toString());
}
jsonWriter.endArray();
}
/* package */ static void writeJson(final SharedPreferences prefs, final JsonWriter jsonWriter)
throws IOException {
jsonWriter.beginObject();
for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
jsonWriter.name(entry.getKey());
final Object innerValue = entry.getValue();
if (innerValue == null) {
jsonWriter.nullValue();
} else if (innerValue instanceof Boolean) {
jsonWriter.value((Boolean) innerValue);
} else if (innerValue instanceof Number) {
jsonWriter.value((Number) innerValue);
} else {
jsonWriter.value(innerValue.toString());
}
}
jsonWriter.endObject();
}
/* package */ static void writeJson(final Key[] keys, final JsonWriter jsonWriter)
throws IOException {
jsonWriter.beginArray();
for (Key key : keys) {
writeJson(key, jsonWriter);
}
jsonWriter.endArray();
}
private static void writeJson(final Key key, final JsonWriter jsonWriter) throws IOException {
jsonWriter.beginObject();
jsonWriter.name("code").value(key.getCode());
jsonWriter.name("altCode").value(key.getAltCode());
jsonWriter.name("x").value(key.getX());
jsonWriter.name("y").value(key.getY());
jsonWriter.name("w").value(key.getWidth());
jsonWriter.name("h").value(key.getHeight());
jsonWriter.endObject();
}
/* package */ static void writeJson(final SuggestedWords words, final JsonWriter jsonWriter)
throws IOException {
jsonWriter.beginObject();
jsonWriter.name("typedWordValid").value(words.mTypedWordValid);
jsonWriter.name("willAutoCorrect")
.value(words.mWillAutoCorrect);
jsonWriter.name("isPunctuationSuggestions")
.value(words.isPunctuationSuggestions());
jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
jsonWriter.name("isPrediction").value(words.mIsPrediction);
jsonWriter.name("suggestedWords");
jsonWriter.beginArray();
final int size = words.size();
for (int j = 0; j < size; j++) {
final SuggestedWordInfo wordInfo = words.getInfo(j);
jsonWriter.beginObject();
jsonWriter.name("word").value(wordInfo.toString());
jsonWriter.name("score").value(wordInfo.mScore);
jsonWriter.name("kind").value(wordInfo.getKind());
jsonWriter.name("sourceDict").value(wordInfo.mSourceDict.mDictType);
jsonWriter.endObject();
}
jsonWriter.endArray();
jsonWriter.endObject();
}
/* package */ static void writeJson(final MotionEvent me, final JsonWriter jsonWriter)
throws IOException {
jsonWriter.beginObject();
jsonWriter.name("pointerIds");
jsonWriter.beginArray();
final int pointerCount = me.getPointerCount();
for (int index = 0; index < pointerCount; index++) {
jsonWriter.value(me.getPointerId(index));
}
jsonWriter.endArray();
jsonWriter.name("xyt");
jsonWriter.beginArray();
final int historicalSize = me.getHistorySize();
for (int index = 0; index < historicalSize; index++) {
jsonWriter.beginObject();
jsonWriter.name("t");
jsonWriter.value(me.getHistoricalEventTime(index));
jsonWriter.name("d");
jsonWriter.beginArray();
for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
jsonWriter.beginObject();
jsonWriter.name("x");
jsonWriter.value(me.getHistoricalX(pointerIndex, index));
jsonWriter.name("y");
jsonWriter.value(me.getHistoricalY(pointerIndex, index));
jsonWriter.endObject();
}
jsonWriter.endArray();
jsonWriter.endObject();
}
jsonWriter.beginObject();
jsonWriter.name("t");
jsonWriter.value(me.getEventTime());
jsonWriter.name("d");
jsonWriter.beginArray();
for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
jsonWriter.beginObject();
jsonWriter.name("x");
jsonWriter.value(me.getX(pointerIndex));
jsonWriter.name("y");
jsonWriter.value(me.getY(pointerIndex));
jsonWriter.endObject();
}
jsonWriter.endArray();
jsonWriter.endObject();
jsonWriter.endArray();
jsonWriter.endObject();
}
}

View File

@ -1,73 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import java.util.LinkedList;
/**
* Maintain a FIFO queue of LogUnits.
*
* This class provides an unbounded queue. This is useful when the user is aware that their actions
* are being recorded, such as when they are trying to reproduce a bug. In this case, there should
* not be artificial restrictions on how many events that can be saved.
*/
public class LogBuffer {
// TODO: Gracefully handle situations in which this LogBuffer is consuming too much memory.
// This may happen, for example, if the user has forgotten that data is being logged.
private final LinkedList<LogUnit> mLogUnits;
public LogBuffer() {
mLogUnits = new LinkedList<>();
}
protected LinkedList<LogUnit> getLogUnits() {
return mLogUnits;
}
public void clear() {
mLogUnits.clear();
}
public void shiftIn(final LogUnit logUnit) {
mLogUnits.add(logUnit);
}
public LogUnit unshiftIn() {
if (mLogUnits.isEmpty()) {
return null;
}
return mLogUnits.removeLast();
}
public LogUnit peekLastLogUnit() {
if (mLogUnits.isEmpty()) {
return null;
}
return mLogUnits.peekLast();
}
public boolean isEmpty() {
return mLogUnits.isEmpty();
}
public LogUnit shiftOut() {
if (isEmpty()) {
return null;
}
return mLogUnits.removeFirst();
}
}

View File

@ -1,225 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.content.SharedPreferences;
import android.util.JsonWriter;
import android.util.Log;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.IOException;
/**
* A template for typed information stored in the logs.
*
* A LogStatement contains a name, keys, and flags about whether the {@code Object[] values}
* associated with the {@code String[] keys} are likely to reveal information about the user. The
* actual values are stored separately.
*/
public class LogStatement {
private static final String TAG = LogStatement.class.getSimpleName();
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
// Constants for particular statements
public static final String TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT =
"PointerTrackerCallListenerOnCodeInput";
public static final String KEY_CODE = "code";
public static final String VALUE_RESEARCH = "research";
public static final String TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS =
"MainKeyboardViewOnLongPress";
public static final String ACTION = "action";
public static final String VALUE_DOWN = "DOWN";
public static final String TYPE_MOTION_EVENT = "MotionEvent";
public static final String KEY_IS_LOGGING_RELATED = "isLoggingRelated";
// Keys for internal key/value pairs
private static final String CURRENT_TIME_KEY = "_ct";
private static final String UPTIME_KEY = "_ut";
private static final String EVENT_TYPE_KEY = "_ty";
// Name specifying the LogStatement type.
private final String mType;
// mIsPotentiallyPrivate indicates that event contains potentially private information. If
// the word that this event is a part of is determined to be privacy-sensitive, then this
// event should not be included in the output log. The system waits to output until the
// containing word is known.
private final boolean mIsPotentiallyPrivate;
// mIsPotentiallyRevealing indicates that this statement may disclose details about other
// words typed in other LogUnits. This can happen if the user is not inserting spaces, and
// data from Suggestions and/or Composing text reveals the entire "megaword". For example,
// say the user is typing "for the win", and the system wants to record the bigram "the
// win". If the user types "forthe", omitting the space, the system will give "for the" as
// a suggestion. If the user accepts the autocorrection, the suggestion for "for the" is
// included in the log for the word "the", disclosing that the previous word had been "for".
// For now, we simply do not include this data when logging part of a "megaword".
private final boolean mIsPotentiallyRevealing;
// mKeys stores the names that are the attributes in the output json objects
private final String[] mKeys;
private static final String[] NULL_KEYS = new String[0];
LogStatement(final String name, final boolean isPotentiallyPrivate,
final boolean isPotentiallyRevealing, final String... keys) {
mType = name;
mIsPotentiallyPrivate = isPotentiallyPrivate;
mIsPotentiallyRevealing = isPotentiallyRevealing;
mKeys = (keys == null) ? NULL_KEYS : keys;
}
public String getType() {
return mType;
}
public boolean isPotentiallyPrivate() {
return mIsPotentiallyPrivate;
}
public boolean isPotentiallyRevealing() {
return mIsPotentiallyRevealing;
}
public String[] getKeys() {
return mKeys;
}
/**
* Utility function to test whether a key-value pair exists in a LogStatement.
*
* A LogStatement is really just a template -- it does not contain the values, only the
* keys. So the values must be passed in as an argument.
*
* @param queryKey the String that is tested by {@code String.equals()} to the keys in the
* LogStatement
* @param queryValue an Object that must be {@code Object.equals()} to the key's corresponding
* value in the {@code values} array
* @param values the values corresponding to mKeys
*
* @returns {@true} if {@code queryKey} exists in the keys for this LogStatement, and {@code
* queryValue} matches the corresponding value in {@code values}
*
* @throws IllegalArgumentException if {@code values.length} is not equal to keys().length()
*/
public boolean containsKeyValuePair(final String queryKey, final Object queryValue,
final Object[] values) {
if (mKeys.length != values.length) {
throw new IllegalArgumentException("Mismatched number of keys and values.");
}
final int length = mKeys.length;
for (int i = 0; i < length; i++) {
if (mKeys[i].equals(queryKey) && values[i].equals(queryValue)) {
return true;
}
}
return false;
}
/**
* Utility function to set a value in a LogStatement.
*
* A LogStatement is really just a template -- it does not contain the values, only the
* keys. So the values must be passed in as an argument.
*
* @param queryKey the String that is tested by {@code String.equals()} to the keys in the
* LogStatement
* @param values the array of values corresponding to mKeys
* @param newValue the replacement value to go into the {@code values} array
*
* @returns {@true} if the key exists and the value was successfully set, {@false} otherwise
*
* @throws IllegalArgumentException if {@code values.length} is not equal to keys().length()
*/
public boolean setValue(final String queryKey, final Object[] values, final Object newValue) {
if (mKeys.length != values.length) {
throw new IllegalArgumentException("Mismatched number of keys and values.");
}
final int length = mKeys.length;
for (int i = 0; i < length; i++) {
if (mKeys[i].equals(queryKey)) {
values[i] = newValue;
return true;
}
}
return false;
}
/**
* Write the contents out through jsonWriter.
*
* The JsonWriter class must have already had {@code JsonWriter.beginArray} called on it.
*
* Note that this method is not thread safe for the same jsonWriter. Callers must ensure
* thread safety.
*/
public boolean outputToLocked(final JsonWriter jsonWriter, final Long time,
final Object... values) {
if (DEBUG) {
if (mKeys.length != values.length) {
Log.d(TAG, "Key and Value list sizes do not match. " + mType);
}
}
try {
jsonWriter.beginObject();
jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
jsonWriter.name(UPTIME_KEY).value(time);
jsonWriter.name(EVENT_TYPE_KEY).value(mType);
final int length = values.length;
for (int i = 0; i < length; i++) {
jsonWriter.name(mKeys[i]);
final Object value = values[i];
if (value instanceof CharSequence) {
jsonWriter.value(value.toString());
} else if (value instanceof Number) {
jsonWriter.value((Number) value);
} else if (value instanceof Boolean) {
jsonWriter.value((Boolean) value);
} else if (value instanceof CompletionInfo[]) {
JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter);
} else if (value instanceof SharedPreferences) {
JsonUtils.writeJson((SharedPreferences) value, jsonWriter);
} else if (value instanceof Key[]) {
JsonUtils.writeJson((Key[]) value, jsonWriter);
} else if (value instanceof SuggestedWords) {
JsonUtils.writeJson((SuggestedWords) value, jsonWriter);
} else if (value instanceof MotionEvent) {
JsonUtils.writeJson((MotionEvent) value, jsonWriter);
} else if (value == null) {
jsonWriter.nullValue();
} else {
if (DEBUG) {
Log.w(TAG, "Unrecognized type to be logged: "
+ (value == null ? "<null>" : value.getClass().getName()));
}
jsonWriter.nullValue();
}
}
jsonWriter.endObject();
} catch (IOException e) {
e.printStackTrace();
Log.w(TAG, "Error in JsonWriter; skipping LogStatement");
return false;
}
return true;
}
}

View File

@ -1,496 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.JsonWriter;
import android.util.Log;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* A group of log statements related to each other.
*
* A LogUnit is collection of LogStatements, each of which is generated by at a particular point
* in the code. (There is no LogStatement class; the data is stored across the instance variables
* here.) A single LogUnit's statements can correspond to all the calls made while in the same
* composing region, or all the calls between committing the last composing region, and the first
* character of the next composing region.
*
* Individual statements in a log may be marked as potentially private. If so, then they are only
* published to a ResearchLog if the ResearchLogger determines that publishing the entire LogUnit
* will not violate the user's privacy. Checks for this may include whether other LogUnits have
* been published recently, or whether the LogUnit contains numbers, etc.
*/
public class LogUnit {
private static final String TAG = LogUnit.class.getSimpleName();
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private final ArrayList<LogStatement> mLogStatementList;
private final ArrayList<Object[]> mValuesList;
// Assume that mTimeList is sorted in increasing order. Do not insert null values into
// mTimeList.
private final ArrayList<Long> mTimeList;
// Words that this LogUnit generates. Should be null if the data in the LogUnit does not
// generate a genuine word (i.e. separators alone do not count as a word). Should never be
// empty. Note that if the user types spaces explicitly, then normally mWords should contain
// only a single word; it will only contain space-separate multiple words if the user does not
// enter a space, and the system enters one automatically.
private String mWords;
private String[] mWordArray = EMPTY_STRING_ARRAY;
private boolean mMayContainDigit;
private boolean mIsPartOfMegaword;
private boolean mContainsUserDeletions;
// mCorrectionType indicates whether the word was corrected at all, and if so, the nature of the
// correction.
private int mCorrectionType;
// LogUnits start in this state. If a word is entered without being corrected, it will have
// this CorrectiontType.
public static final int CORRECTIONTYPE_NO_CORRECTION = 0;
// The LogUnit was corrected manually by the user in an unspecified way.
public static final int CORRECTIONTYPE_CORRECTION = 1;
// The LogUnit was corrected manually by the user to a word not in the list of suggestions of
// the first word typed here. (Note: this is a heuristic value, it may be incorrect, for
// example, if the user repositions the cursor).
public static final int CORRECTIONTYPE_DIFFERENT_WORD = 2;
// The LogUnit was corrected manually by the user to a word that was in the list of suggestions
// of the first word typed here. (Again, a heuristic). It is probably a typo correction.
public static final int CORRECTIONTYPE_TYPO = 3;
// TODO: Rather than just tracking the current state, keep a historical record of the LogUnit's
// state and statistics. This should include how many times it has been corrected, whether
// other LogUnit edits were done between edits to this LogUnit, etc. Also track when a LogUnit
// previously contained a word, but was corrected to empty (because it was deleted, and there is
// no known replacement).
private SuggestedWords mSuggestedWords;
public LogUnit() {
mLogStatementList = new ArrayList<>();
mValuesList = new ArrayList<>();
mTimeList = new ArrayList<>();
mIsPartOfMegaword = false;
mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
mSuggestedWords = null;
}
private LogUnit(final ArrayList<LogStatement> logStatementList,
final ArrayList<Object[]> valuesList,
final ArrayList<Long> timeList,
final boolean isPartOfMegaword) {
mLogStatementList = logStatementList;
mValuesList = valuesList;
mTimeList = timeList;
mIsPartOfMegaword = isPartOfMegaword;
mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
mSuggestedWords = null;
}
private static final Object[] NULL_VALUES = new Object[0];
/**
* Adds a new log statement. The time parameter in successive calls to this method must be
* monotonically increasing, or splitByTime() will not work.
*/
public void addLogStatement(final LogStatement logStatement, final long time,
Object... values) {
if (values == null) {
values = NULL_VALUES;
}
mLogStatementList.add(logStatement);
mValuesList.add(values);
mTimeList.add(time);
}
/**
* Publish the contents of this LogUnit to {@code researchLog}.
*
* For each publishable {@code LogStatement}, invoke {@link LogStatement#outputToLocked}.
*
* @param researchLog where to publish the contents of this {@code LogUnit}
* @param canIncludePrivateData whether the private data in this {@code LogUnit} should be
* included
*
* @throws IOException if publication to the log file is not possible
*/
public synchronized void publishTo(final ResearchLog researchLog,
final boolean canIncludePrivateData) throws IOException {
// Write out any logStatement that passes the privacy filter.
final int size = mLogStatementList.size();
if (size != 0) {
// Note that jsonWriter is only set to a non-null value if the logUnit start text is
// output and at least one logStatement is output.
JsonWriter jsonWriter = researchLog.getInitializedJsonWriterLocked();
outputLogUnitStart(jsonWriter, canIncludePrivateData);
for (int i = 0; i < size; i++) {
final LogStatement logStatement = mLogStatementList.get(i);
if (!canIncludePrivateData && logStatement.isPotentiallyPrivate()) {
continue;
}
if (mIsPartOfMegaword && logStatement.isPotentiallyRevealing()) {
continue;
}
logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i));
}
outputLogUnitStop(jsonWriter);
}
}
private static final String WORD_KEY = "_wo";
private static final String NUM_WORDS_KEY = "_nw";
private static final String CORRECTION_TYPE_KEY = "_corType";
private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart";
private static final String LOG_UNIT_END_KEY = "logUnitEnd";
final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA =
new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY,
NUM_WORDS_KEY);
final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA =
new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
false /* isPotentiallyRevealing */, NUM_WORDS_KEY);
private void outputLogUnitStart(final JsonWriter jsonWriter,
final boolean canIncludePrivateData) {
final LogStatement logStatement;
if (canIncludePrivateData) {
LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA.outputToLocked(jsonWriter,
SystemClock.uptimeMillis(), getWordsAsString(), getCorrectionType(),
getNumWords());
} else {
LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter,
SystemClock.uptimeMillis(), getNumWords());
}
}
final LogStatement LOGSTATEMENT_LOG_UNIT_END =
new LogStatement(LOG_UNIT_END_KEY, false /* isPotentiallyPrivate */,
false /* isPotentiallyRevealing */);
private void outputLogUnitStop(final JsonWriter jsonWriter) {
LOGSTATEMENT_LOG_UNIT_END.outputToLocked(jsonWriter, SystemClock.uptimeMillis());
}
/**
* Mark the current logUnit as containing data to generate {@code newWords}.
*
* If {@code setWord()} was previously called for this LogUnit, then the method will try to
* determine what kind of correction it is, and update its internal state of the correctionType
* accordingly.
*
* @param newWords The words this LogUnit generates. Caller should not pass null or the empty
* string.
*/
public void setWords(final String newWords) {
if (hasOneOrMoreWords()) {
// The word was already set once, and it is now being changed. See if the new word
// is close to the old word. If so, then the change is probably a typo correction.
// If not, the user may have decided to enter a different word, so flag it.
if (mSuggestedWords != null) {
if (isInSuggestedWords(newWords, mSuggestedWords)) {
mCorrectionType = CORRECTIONTYPE_TYPO;
} else {
mCorrectionType = CORRECTIONTYPE_DIFFERENT_WORD;
}
} else {
// No suggested words, so it's not clear whether it's a typo or different word.
// Mark it as a generic correction.
mCorrectionType = CORRECTIONTYPE_CORRECTION;
}
} else {
mCorrectionType = CORRECTIONTYPE_NO_CORRECTION;
}
mWords = newWords;
// Update mWordArray
mWordArray = (TextUtils.isEmpty(mWords)) ? EMPTY_STRING_ARRAY
: WHITESPACE_PATTERN.split(mWords);
if (mWordArray.length > 0 && TextUtils.isEmpty(mWordArray[0])) {
// Empty string at beginning of array. Must have been whitespace at the start of the
// word. Remove the empty string.
mWordArray = Arrays.copyOfRange(mWordArray, 1, mWordArray.length);
}
}
public String getWordsAsString() {
return mWords;
}
/**
* Retuns the words generated by the data in this LogUnit.
*
* The first word may be an empty string, if the data in the LogUnit started by generating
* whitespace.
*
* @return the array of words. an empty list of there are no words associated with this LogUnit.
*/
public String[] getWordsAsStringArray() {
return mWordArray;
}
public boolean hasOneOrMoreWords() {
return mWordArray.length >= 1;
}
public int getNumWords() {
return mWordArray.length;
}
// TODO: Refactor to eliminate getter/setters
public void setMayContainDigit() {
mMayContainDigit = true;
}
// TODO: Refactor to eliminate getter/setters
public boolean mayContainDigit() {
return mMayContainDigit;
}
// TODO: Refactor to eliminate getter/setters
public void setContainsUserDeletions() {
mContainsUserDeletions = true;
}
// TODO: Refactor to eliminate getter/setters
public boolean containsUserDeletions() {
return mContainsUserDeletions;
}
// TODO: Refactor to eliminate getter/setters
public void setCorrectionType(final int correctionType) {
mCorrectionType = correctionType;
}
// TODO: Refactor to eliminate getter/setters
public int getCorrectionType() {
return mCorrectionType;
}
public boolean isEmpty() {
return mLogStatementList.isEmpty();
}
/**
* Split this logUnit, with all events before maxTime staying in the current logUnit, and all
* events after maxTime going into a new LogUnit that is returned.
*/
public LogUnit splitByTime(final long maxTime) {
// Assume that mTimeList is in sorted order.
final int length = mTimeList.size();
// TODO: find time by binary search, e.g. using Collections#binarySearch()
for (int index = 0; index < length; index++) {
if (mTimeList.get(index) > maxTime) {
final List<LogStatement> laterLogStatements =
mLogStatementList.subList(index, length);
final List<Object[]> laterValues = mValuesList.subList(index, length);
final List<Long> laterTimes = mTimeList.subList(index, length);
// Create the LogUnit containing the later logStatements and associated data.
final LogUnit newLogUnit = new LogUnit(
new ArrayList<>(laterLogStatements),
new ArrayList<>(laterValues),
new ArrayList<>(laterTimes),
true /* isPartOfMegaword */);
newLogUnit.mWords = null;
newLogUnit.mMayContainDigit = mMayContainDigit;
newLogUnit.mContainsUserDeletions = mContainsUserDeletions;
// Purge the logStatements and associated data from this LogUnit.
laterLogStatements.clear();
laterValues.clear();
laterTimes.clear();
mIsPartOfMegaword = true;
return newLogUnit;
}
}
return new LogUnit();
}
public void append(final LogUnit logUnit) {
mLogStatementList.addAll(logUnit.mLogStatementList);
mValuesList.addAll(logUnit.mValuesList);
mTimeList.addAll(logUnit.mTimeList);
mWords = null;
if (logUnit.mWords != null) {
setWords(logUnit.mWords);
}
mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit;
mContainsUserDeletions = mContainsUserDeletions || logUnit.mContainsUserDeletions;
mIsPartOfMegaword = false;
}
public SuggestedWords getSuggestions() {
return mSuggestedWords;
}
/**
* Initialize the suggestions.
*
* Once set to a non-null value, the suggestions may not be changed again. This is to keep
* track of the list of words that are close to the user's initial effort to type the word.
* Only words that are close to the initial effort are considered typo corrections.
*/
public void initializeSuggestions(final SuggestedWords suggestedWords) {
if (mSuggestedWords == null) {
mSuggestedWords = suggestedWords;
}
}
private static boolean isInSuggestedWords(final String queryWord,
final SuggestedWords suggestedWords) {
if (TextUtils.isEmpty(queryWord)) {
return false;
}
final int size = suggestedWords.size();
for (int i = 0; i < size; i++) {
final SuggestedWordInfo wordInfo = suggestedWords.getInfo(i);
if (queryWord.equals(wordInfo.mWord)) {
return true;
}
}
return false;
}
/**
* Remove data associated with selecting the Research button.
*
* A LogUnit will capture all user interactions with the IME, including the "meta-interactions"
* of using the Research button to control the logging (e.g. by starting and stopping recording
* of a test case). Because meta-interactions should not be part of the normal log, calling
* this method will set a field in the LogStatements of the motion events to indiciate that
* they should be disregarded.
*
* This implementation assumes that the data recorded by the meta-interaction takes the
* form of all events following the first MotionEvent.ACTION_DOWN before the first long-press
* before the last onCodeEvent containing a code matching {@code LogStatement.VALUE_RESEARCH}.
*
* @returns true if data was removed
*/
public boolean removeResearchButtonInvocation() {
// This method is designed to be idempotent.
// First, find last invocation of "research" key
final int indexOfLastResearchKey = findLastIndexContainingKeyValue(
LogStatement.TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT,
LogStatement.KEY_CODE, LogStatement.VALUE_RESEARCH);
if (indexOfLastResearchKey < 0) {
// Could not find invocation of "research" key. Leave log as is.
if (DEBUG) {
Log.d(TAG, "Could not find research key");
}
return false;
}
// Look for the long press that started the invocation of the research key code input.
final int indexOfLastLongPressBeforeResearchKey =
findLastIndexBefore(LogStatement.TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS,
indexOfLastResearchKey);
// Look for DOWN event preceding the long press
final int indexOfLastDownEventBeforeLongPress =
findLastIndexContainingKeyValueBefore(LogStatement.TYPE_MOTION_EVENT,
LogStatement.ACTION, LogStatement.VALUE_DOWN,
indexOfLastLongPressBeforeResearchKey);
// Flag all LatinKeyboardViewProcessMotionEvents from the DOWN event to the research key as
// logging-related
final int startingIndex = indexOfLastDownEventBeforeLongPress == -1 ? 0
: indexOfLastDownEventBeforeLongPress;
for (int index = startingIndex; index < indexOfLastResearchKey; index++) {
final LogStatement logStatement = mLogStatementList.get(index);
final String type = logStatement.getType();
final Object[] values = mValuesList.get(index);
if (type.equals(LogStatement.TYPE_MOTION_EVENT)) {
logStatement.setValue(LogStatement.KEY_IS_LOGGING_RELATED, values, true);
}
}
return true;
}
/**
* Find the index of the last LogStatement before {@code startingIndex} of type {@code type}.
*
* @param queryType a String that must be {@code String.equals()} to the LogStatement type
* @param startingIndex the index to start the backward search from. Must be less than the
* length of mLogStatementList, or an IndexOutOfBoundsException is thrown. Can be negative,
* in which case -1 is returned.
*
* @return The index of the last LogStatement, -1 if none exists.
*/
private int findLastIndexBefore(final String queryType, final int startingIndex) {
return findLastIndexContainingKeyValueBefore(queryType, null, null, startingIndex);
}
/**
* Find the index of the last LogStatement before {@code startingIndex} of type {@code type}
* containing the given key-value pair.
*
* @param queryType a String that must be {@code String.equals()} to the LogStatement type
* @param queryKey a String that must be {@code String.equals()} to a key in the LogStatement
* @param queryValue an Object that must be {@code String.equals()} to the key's corresponding
* value
*
* @return The index of the last LogStatement, -1 if none exists.
*/
private int findLastIndexContainingKeyValue(final String queryType, final String queryKey,
final Object queryValue) {
return findLastIndexContainingKeyValueBefore(queryType, queryKey, queryValue,
mLogStatementList.size() - 1);
}
/**
* Find the index of the last LogStatement before {@code startingIndex} of type {@code type}
* containing the given key-value pair.
*
* @param queryType a String that must be {@code String.equals()} to the LogStatement type
* @param queryKey a String that must be {@code String.equals()} to a key in the LogStatement
* @param queryValue an Object that must be {@code String.equals()} to the key's corresponding
* value
* @param startingIndex the index to start the backward search from. Must be less than the
* length of mLogStatementList, or an IndexOutOfBoundsException is thrown. Can be negative,
* in which case -1 is returned.
*
* @return The index of the last LogStatement, -1 if none exists.
*/
private int findLastIndexContainingKeyValueBefore(final String queryType, final String queryKey,
final Object queryValue, final int startingIndex) {
if (startingIndex < 0) {
return -1;
}
for (int index = startingIndex; index >= 0; index--) {
final LogStatement logStatement = mLogStatementList.get(index);
final String type = logStatement.getType();
if (type.equals(queryType) && (queryKey == null
|| logStatement.containsKeyValuePair(queryKey, queryValue,
mValuesList.get(index)))) {
return index;
}
}
return -1;
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.view.MotionEvent;
/* package */ class LoggingUtils {
private LoggingUtils() {
// This utility class is not publicly instantiable.
}
/* package */ static String getMotionEventActionTypeString(final int actionType) {
switch (actionType) {
case MotionEvent.ACTION_CANCEL: return "CANCEL";
case MotionEvent.ACTION_UP: return "UP";
case MotionEvent.ACTION_DOWN: return "DOWN";
case MotionEvent.ACTION_POINTER_UP: return "POINTER_UP";
case MotionEvent.ACTION_POINTER_DOWN: return "POINTER_DOWN";
case MotionEvent.ACTION_MOVE: return "MOVE";
case MotionEvent.ACTION_OUTSIDE: return "OUTSIDE";
default: return "ACTION_" + actionType;
}
}
}

View File

@ -1,287 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* MainLogBuffer is a FixedLogBuffer that tracks the state of LogUnits to make privacy guarantees.
*
* There are three forms of privacy protection: 1) only words in the main dictionary are allowed to
* be logged in enough detail to determine their contents, 2) only a subset of words are logged
* in detail, such as 10%, and 3) no numbers are logged.
*
* This class maintains a list of LogUnits, each corresponding to a word. As the user completes
* words, they are added here. But if the user backs up over their current word to edit a word
* entered earlier, then it is pulled out of this LogBuffer, changes are then added to the end of
* the LogUnit, and it is pushed back in here when the user is done. Because words may be pulled
* back out even after they are pushed in, we must not publish the contents of this LogBuffer too
* quickly. However, we cannot let the contents pile up either, or it will limit the editing that
* a user can perform.
*
* To balance these requirements (keep history so user can edit, flush history so it does not pile
* up), the LogBuffer is considered "complete" when the user has entered enough words to form an
* n-gram, followed by enough additional non-detailed words (that are in the 90%, as per above).
* Once complete, the n-gram may be published to flash storage (via the ResearchLog class).
* However, the additional non-detailed words are retained, in case the user backspaces to edit
* them. The MainLogBuffer then continues to add words, publishing individual non-detailed words
* as new words arrive. After enough non-detailed words have been pushed out to account for the
* 90% between words, the words at the front of the LogBuffer can be published as an n-gram again.
*
* If the words that would form the valid n-gram are not in the dictionary, then words are pushed
* through the LogBuffer one at a time until an n-gram is found that is entirely composed of
* dictionary words.
*
* If the user closes a session, then the entire LogBuffer is flushed, publishing any embedded
* n-gram containing dictionary words.
*/
public abstract class MainLogBuffer extends FixedLogBuffer {
private static final String TAG = MainLogBuffer.class.getSimpleName();
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
// Keep consistent with switch statement in Statistics.recordPublishabilityResultCode()
public static final int PUBLISHABILITY_PUBLISHABLE = 0;
public static final int PUBLISHABILITY_UNPUBLISHABLE_STOPPING = 1;
public static final int PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT = 2;
public static final int PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY = 3;
public static final int PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE = 4;
public static final int PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT = 5;
public static final int PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY = 6;
// The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
public static final int N_GRAM_SIZE = 2;
private final DictionaryFacilitator mDictionaryFacilitator;
@UsedForTesting
private Dictionary mDictionaryForTesting;
private boolean mIsStopping = false;
/* package for test */ int mNumWordsBetweenNGrams;
// Counter for words left to suppress before an n-gram can be sampled. Reset to mMinWordPeriod
// after a sample is taken.
/* package for test */ int mNumWordsUntilSafeToSample;
public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore,
final DictionaryFacilitator dictionaryFacilitator) {
super(N_GRAM_SIZE + wordsBetweenSamples);
mNumWordsBetweenNGrams = wordsBetweenSamples;
mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
mDictionaryFacilitator = dictionaryFacilitator;
}
@UsedForTesting
/* package for test */ void setDictionaryForTesting(final Dictionary dictionary) {
mDictionaryForTesting = dictionary;
}
private boolean isValidDictWord(final String word) {
if (mDictionaryForTesting != null) {
return mDictionaryForTesting.isValidWord(word);
}
if (mDictionaryFacilitator != null) {
return mDictionaryFacilitator.isValidMainDictWord(word);
}
return false;
}
public void setIsStopping() {
mIsStopping = true;
}
/**
* Determines whether the string determined by a series of LogUnits will not violate user
* privacy if published.
*
* @param logUnits a LogUnit list to check for publishability
* @param nGramSize the smallest n-gram acceptable to be published. if
* {@link ResearchLogger#IS_LOGGING_EVERYTHING} is true, then publish if there are more than
* {@code minNGramSize} words in the logUnits, otherwise wait. if {@link
* ResearchLogger#IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize
* words in the LogUnits.
*
* @return one of the {@code PUBLISHABILITY_*} result codes defined in this class.
*/
private int getPublishabilityResultCode(final ArrayList<LogUnit> logUnits,
final int nGramSize) {
// Bypass privacy checks when debugging.
if (ResearchLogger.IS_LOGGING_EVERYTHING) {
if (mIsStopping) {
return PUBLISHABILITY_UNPUBLISHABLE_STOPPING;
}
// Only check that it is the right length. If not, wait for later words to make
// complete n-grams.
int numWordsInLogUnitList = 0;
final int length = logUnits.size();
for (int i = 0; i < length; i++) {
final LogUnit logUnit = logUnits.get(i);
numWordsInLogUnitList += logUnit.getNumWords();
}
if (numWordsInLogUnitList >= nGramSize) {
return PUBLISHABILITY_PUBLISHABLE;
} else {
return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
}
}
// Check that we are not sampling too frequently. Having sampled recently might disclose
// too much of the user's intended meaning.
if (mNumWordsUntilSafeToSample > 0) {
return PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY;
}
// Reload the dictionary in case it has changed (e.g., because the user has changed
// languages).
if ((mDictionaryFacilitator == null
|| !mDictionaryFacilitator.hasInitializedMainDictionary())
&& mDictionaryForTesting == null) {
// Main dictionary is unavailable. Since we cannot check it, we cannot tell if a
// word is out-of-vocabulary or not. Therefore, we must judge the entire buffer
// contents to potentially pose a privacy risk.
return PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE;
}
// Check each word in the buffer. If any word poses a privacy threat, we cannot upload
// the complete buffer contents in detail.
int numWordsInLogUnitList = 0;
for (final LogUnit logUnit : logUnits) {
if (!logUnit.hasOneOrMoreWords()) {
// Digits outside words are a privacy threat.
if (logUnit.mayContainDigit()) {
return PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT;
}
} else {
numWordsInLogUnitList += logUnit.getNumWords();
final String[] words = logUnit.getWordsAsStringArray();
for (final String word : words) {
// Words not in the dictionary are a privacy threat.
if (ResearchLogger.hasLetters(word) && !isValidDictWord(word)) {
if (DEBUG) {
Log.d(TAG, "\"" + word + "\" NOT SAFE!: hasLetters: "
+ ResearchLogger.hasLetters(word)
+ ", isValid: " + isValidDictWord(word));
}
return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY;
}
}
}
}
// Finally, only return true if the ngram is the right size.
if (numWordsInLogUnitList == nGramSize) {
return PUBLISHABILITY_PUBLISHABLE;
} else {
return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
}
}
public void shiftAndPublishAll() throws IOException {
final LinkedList<LogUnit> logUnits = getLogUnits();
while (!logUnits.isEmpty()) {
publishLogUnitsAtFrontOfBuffer();
}
}
@Override
protected final void onBufferFull() {
try {
publishLogUnitsAtFrontOfBuffer();
} catch (final IOException e) {
if (DEBUG) {
Log.w(TAG, "IOException when publishing front of LogBuffer", e);
}
}
}
/**
* If there is a safe n-gram at the front of this log buffer, publish it with all details, and
* remove the LogUnits that constitute it.
*
* An n-gram might not be "safe" if it violates privacy controls. E.g., it might contain
* numbers, an out-of-vocabulary word, or another n-gram may have been published recently. If
* there is no safe n-gram, then the LogUnits up through the first word-containing LogUnit are
* published, but without disclosing any privacy-related details, such as the word the LogUnit
* generated, motion data, etc.
*
* Note that a LogUnit can hold more than one word if the user types without explicit spaces.
* In this case, the words may be grouped together in such a way that pulling an n-gram off the
* front would require splitting a LogUnit. Splitting a LogUnit is not possible, so this case
* is treated just as the unsafe n-gram case. This may cause n-grams to be sampled at slightly
* less than the target frequency.
*/
protected final void publishLogUnitsAtFrontOfBuffer() throws IOException {
// TODO: Refactor this method to require fewer passes through the LogUnits. Should really
// require only one pass.
ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE);
final int publishabilityResultCode = getPublishabilityResultCode(logUnits, N_GRAM_SIZE);
ResearchLogger.recordPublishabilityResultCode(publishabilityResultCode);
if (publishabilityResultCode == MainLogBuffer.PUBLISHABILITY_PUBLISHABLE) {
// Good n-gram at the front of the buffer. Publish it, disclosing details.
publish(logUnits, true /* canIncludePrivateData */);
shiftOutWords(N_GRAM_SIZE);
mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams;
return;
}
// No good n-gram at front, and buffer is full. Shift out up through the first logUnit
// with associated words (or if there is none, all the existing logUnits).
logUnits.clear();
LogUnit logUnit = shiftOut();
while (logUnit != null) {
logUnits.add(logUnit);
final int numWords = logUnit.getNumWords();
if (numWords > 0) {
mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWords);
break;
}
logUnit = shiftOut();
}
publish(logUnits, false /* canIncludePrivateData */);
}
/**
* Called when a list of logUnits should be published.
*
* It is the subclass's responsibility to implement the publication.
*
* @param logUnits The list of logUnits to be published.
* @param canIncludePrivateData Whether the private data in the logUnits can be included in
* publication.
*
* @throws IOException if publication to the log file is not possible
*/
protected abstract void publish(final ArrayList<LogUnit> logUnits,
final boolean canIncludePrivateData) throws IOException;
@Override
protected int shiftOutWords(final int numWords) {
final int numWordsShiftedOut = super.shiftOutWords(numWords);
mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWordsShiftedOut);
if (DEBUG) {
Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample);
}
return numWordsShiftedOut;
}
}

View File

@ -1,324 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.inputmethod.research;
import android.util.JsonReader;
import android.util.Log;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
public class MotionEventReader {
private static final String TAG = MotionEventReader.class.getSimpleName();
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
// Assumes that MotionEvent.ACTION_MASK does not have all bits set.`
private static final int UNINITIALIZED_ACTION = ~MotionEvent.ACTION_MASK;
// No legitimate int is negative
private static final int UNINITIALIZED_INT = -1;
// No legitimate long is negative
private static final long UNINITIALIZED_LONG = -1L;
// No legitimate float is negative
private static final float UNINITIALIZED_FLOAT = -1.0f;
public ReplayData readMotionEventData(final File file) {
final ReplayData replayData = new ReplayData();
try {
// Read file
final JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(
new FileInputStream(file))));
jsonReader.beginArray();
while (jsonReader.hasNext()) {
readLogStatement(jsonReader, replayData);
}
jsonReader.endArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return replayData;
}
@UsedForTesting
static class ReplayData {
final ArrayList<Integer> mActions = new ArrayList<>();
final ArrayList<PointerProperties[]> mPointerPropertiesArrays = new ArrayList<>();
final ArrayList<PointerCoords[]> mPointerCoordsArrays = new ArrayList<>();
final ArrayList<Long> mTimes = new ArrayList<>();
}
/**
* Read motion data from a logStatement and store it in {@code replayData}.
*
* Two kinds of logStatements can be read. In the first variant, the MotionEvent data is
* represented as attributes at the top level like so:
*
* <pre>
* {
* "_ct": 1359590400000,
* "_ut": 4381933,
* "_ty": "MotionEvent",
* "action": "UP",
* "isLoggingRelated": false,
* "x": 100,
* "y": 200
* }
* </pre>
*
* In the second variant, there is a separate attribute for the MotionEvent that includes
* historical data if present:
*
* <pre>
* {
* "_ct": 135959040000,
* "_ut": 4382702,
* "_ty": "MotionEvent",
* "action": "MOVE",
* "isLoggingRelated": false,
* "motionEvent": {
* "pointerIds": [
* 0
* ],
* "xyt": [
* {
* "t": 4382551,
* "d": [
* {
* "x": 141.25,
* "y": 151.8485107421875,
* "toma": 101.82337188720703,
* "tomi": 101.82337188720703,
* "o": 0.0
* }
* ]
* },
* {
* "t": 4382559,
* "d": [
* {
* "x": 140.7266082763672,
* "y": 151.8485107421875,
* "toma": 101.82337188720703,
* "tomi": 101.82337188720703,
* "o": 0.0
* }
* ]
* }
* ]
* }
* },
* </pre>
*/
@UsedForTesting
/* package for test */ void readLogStatement(final JsonReader jsonReader,
final ReplayData replayData) throws IOException {
String logStatementType = null;
int actionType = UNINITIALIZED_ACTION;
int x = UNINITIALIZED_INT;
int y = UNINITIALIZED_INT;
long time = UNINITIALIZED_LONG;
boolean isLoggingRelated = false;
jsonReader.beginObject();
while (jsonReader.hasNext()) {
final String key = jsonReader.nextName();
if (key.equals("_ty")) {
logStatementType = jsonReader.nextString();
} else if (key.equals("_ut")) {
time = jsonReader.nextLong();
} else if (key.equals("x")) {
x = jsonReader.nextInt();
} else if (key.equals("y")) {
y = jsonReader.nextInt();
} else if (key.equals("action")) {
final String s = jsonReader.nextString();
if (s.equals("UP")) {
actionType = MotionEvent.ACTION_UP;
} else if (s.equals("DOWN")) {
actionType = MotionEvent.ACTION_DOWN;
} else if (s.equals("MOVE")) {
actionType = MotionEvent.ACTION_MOVE;
}
} else if (key.equals("loggingRelated")) {
isLoggingRelated = jsonReader.nextBoolean();
} else if (logStatementType != null && logStatementType.equals("MotionEvent")
&& key.equals("motionEvent")) {
if (actionType == UNINITIALIZED_ACTION) {
Log.e(TAG, "no actionType assigned in MotionEvent json");
}
// Second variant of LogStatement.
if (isLoggingRelated) {
jsonReader.skipValue();
} else {
readEmbeddedMotionEvent(jsonReader, replayData, actionType);
}
} else {
if (DEBUG) {
Log.w(TAG, "Unknown JSON key in LogStatement: " + key);
}
jsonReader.skipValue();
}
}
jsonReader.endObject();
if (logStatementType != null && time != UNINITIALIZED_LONG && x != UNINITIALIZED_INT
&& y != UNINITIALIZED_INT && actionType != UNINITIALIZED_ACTION
&& logStatementType.equals("MotionEvent") && !isLoggingRelated) {
// First variant of LogStatement.
final PointerProperties pointerProperties = new PointerProperties();
pointerProperties.id = 0;
pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
final PointerProperties[] pointerPropertiesArray = {
pointerProperties
};
final PointerCoords pointerCoords = new PointerCoords();
pointerCoords.x = x;
pointerCoords.y = y;
pointerCoords.pressure = 1.0f;
pointerCoords.size = 1.0f;
final PointerCoords[] pointerCoordsArray = {
pointerCoords
};
addMotionEventData(replayData, actionType, time, pointerPropertiesArray,
pointerCoordsArray);
}
}
private void readEmbeddedMotionEvent(final JsonReader jsonReader, final ReplayData replayData,
final int actionType) throws IOException {
jsonReader.beginObject();
PointerProperties[] pointerPropertiesArray = null;
while (jsonReader.hasNext()) { // pointerIds/xyt
final String name = jsonReader.nextName();
if (name.equals("pointerIds")) {
pointerPropertiesArray = readPointerProperties(jsonReader);
} else if (name.equals("xyt")) {
readPointerData(jsonReader, replayData, actionType, pointerPropertiesArray);
}
}
jsonReader.endObject();
}
private PointerProperties[] readPointerProperties(final JsonReader jsonReader)
throws IOException {
final ArrayList<PointerProperties> pointerPropertiesArrayList = new ArrayList<>();
jsonReader.beginArray();
while (jsonReader.hasNext()) {
final PointerProperties pointerProperties = new PointerProperties();
pointerProperties.id = jsonReader.nextInt();
pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
pointerPropertiesArrayList.add(pointerProperties);
}
jsonReader.endArray();
return pointerPropertiesArrayList.toArray(
new PointerProperties[pointerPropertiesArrayList.size()]);
}
private void readPointerData(final JsonReader jsonReader, final ReplayData replayData,
final int actionType, final PointerProperties[] pointerPropertiesArray)
throws IOException {
if (pointerPropertiesArray == null) {
Log.e(TAG, "PointerIDs must be given before xyt data in json for MotionEvent");
jsonReader.skipValue();
return;
}
long time = UNINITIALIZED_LONG;
jsonReader.beginArray();
while (jsonReader.hasNext()) { // Array of historical data
jsonReader.beginObject();
final ArrayList<PointerCoords> pointerCoordsArrayList = new ArrayList<>();
while (jsonReader.hasNext()) { // Time/data object
final String name = jsonReader.nextName();
if (name.equals("t")) {
time = jsonReader.nextLong();
} else if (name.equals("d")) {
jsonReader.beginArray();
while (jsonReader.hasNext()) { // array of data per pointer
final PointerCoords pointerCoords = readPointerCoords(jsonReader);
if (pointerCoords != null) {
pointerCoordsArrayList.add(pointerCoords);
}
}
jsonReader.endArray();
} else {
jsonReader.skipValue();
}
}
jsonReader.endObject();
// Data was recorded as historical events, but must be split apart into
// separate MotionEvents for replaying
if (time != UNINITIALIZED_LONG) {
addMotionEventData(replayData, actionType, time, pointerPropertiesArray,
pointerCoordsArrayList.toArray(
new PointerCoords[pointerCoordsArrayList.size()]));
} else {
Log.e(TAG, "Time not assigned in json for MotionEvent");
}
}
jsonReader.endArray();
}
private PointerCoords readPointerCoords(final JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
float x = UNINITIALIZED_FLOAT;
float y = UNINITIALIZED_FLOAT;
while (jsonReader.hasNext()) { // x,y
final String name = jsonReader.nextName();
if (name.equals("x")) {
x = (float) jsonReader.nextDouble();
} else if (name.equals("y")) {
y = (float) jsonReader.nextDouble();
} else {
jsonReader.skipValue();
}
}
jsonReader.endObject();
if (Float.compare(x, UNINITIALIZED_FLOAT) == 0
|| Float.compare(y, UNINITIALIZED_FLOAT) == 0) {
Log.w(TAG, "missing x or y value in MotionEvent json");
return null;
}
final PointerCoords pointerCoords = new PointerCoords();
pointerCoords.x = x;
pointerCoords.y = y;
pointerCoords.pressure = 1.0f;
pointerCoords.size = 1.0f;
return pointerCoords;
}
private void addMotionEventData(final ReplayData replayData, final int actionType,
final long time, final PointerProperties[] pointerProperties,
final PointerCoords[] pointerCoords) {
replayData.mActions.add(actionType);
replayData.mTimes.add(time);
replayData.mPointerPropertiesArrays.add(pointerProperties);
replayData.mPointerCoordsArrays.add(pointerCoords);
}
}

View File

@ -1,150 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.MotionEventReader.ReplayData;
/**
* Replays a sequence of motion events in realtime on the screen.
*
* Useful for user inspection of logged data.
*/
public class Replayer {
private static final String TAG = Replayer.class.getSimpleName();
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
private static final long START_TIME_DELAY_MS = 500;
private boolean mIsReplaying = false;
private KeyboardSwitcher mKeyboardSwitcher;
private Replayer() {
}
private static final Replayer sInstance = new Replayer();
public static Replayer getInstance() {
return sInstance;
}
public void setKeyboardSwitcher(final KeyboardSwitcher keyboardSwitcher) {
mKeyboardSwitcher = keyboardSwitcher;
}
private static final int MSG_MOTION_EVENT = 0;
private static final int MSG_DONE = 1;
private static final int COMPLETION_TIME_MS = 500;
// TODO: Support historical events and multi-touch.
public void replay(final ReplayData replayData, final Runnable callback) {
if (mIsReplaying) {
return;
}
mIsReplaying = true;
final int numActions = replayData.mActions.size();
if (DEBUG) {
Log.d(TAG, "replaying " + numActions + " actions");
}
if (numActions == 0) {
mIsReplaying = false;
return;
}
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
// The reference time relative to the times stored in events.
final long origStartTime = replayData.mTimes.get(0);
// The reference time relative to which events are replayed in the present.
final long currentStartTime = SystemClock.uptimeMillis() + START_TIME_DELAY_MS;
// The adjustment needed to translate times from the original recorded time to the current
// time.
final long timeAdjustment = currentStartTime - origStartTime;
final Handler handler = new Handler(Looper.getMainLooper()) {
// Track the time of the most recent DOWN event, to be passed as a parameter when
// constructing a MotionEvent. It's initialized here to the origStartTime, but this is
// only a precaution. The value should be overwritten by the first ACTION_DOWN event
// before the first use of the variable. Note that this may cause the first few events
// to have incorrect {@code downTime}s.
private long mOrigDownTime = origStartTime;
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case MSG_MOTION_EVENT:
final int index = msg.arg1;
final int action = replayData.mActions.get(index);
final PointerProperties[] pointerPropertiesArray =
replayData.mPointerPropertiesArrays.get(index);
final PointerCoords[] pointerCoordsArray =
replayData.mPointerCoordsArrays.get(index);
final long origTime = replayData.mTimes.get(index);
if (action == MotionEvent.ACTION_DOWN) {
mOrigDownTime = origTime;
}
final MotionEvent me = MotionEvent.obtain(mOrigDownTime + timeAdjustment,
origTime + timeAdjustment, action,
pointerPropertiesArray.length, pointerPropertiesArray,
pointerCoordsArray, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0);
mainKeyboardView.processMotionEvent(me);
me.recycle();
break;
case MSG_DONE:
mIsReplaying = false;
ResearchLogger.getInstance().requestIndicatorRedraw();
break;
}
}
};
handler.post(new Runnable() {
@Override
public void run() {
ResearchLogger.getInstance().requestIndicatorRedraw();
}
});
for (int i = 0; i < numActions; i++) {
final Message msg = Message.obtain(handler, MSG_MOTION_EVENT, i, 0);
final long msgTime = replayData.mTimes.get(i) + timeAdjustment;
handler.sendMessageAtTime(msg, msgTime);
if (DEBUG) {
Log.d(TAG, "queuing event at " + msgTime);
}
}
final long presentDoneTime = replayData.mTimes.get(numActions - 1) + timeAdjustment
+ COMPLETION_TIME_MS;
handler.sendMessageAtTime(Message.obtain(handler, MSG_DONE), presentDoneTime);
if (callback != null) {
handler.postAtTime(callback, presentDoneTime + 1);
}
}
public boolean isReplaying() {
return mIsReplaying;
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.inputmethod.research;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import com.android.inputmethod.research.MotionEventReader.ReplayData;
import java.io.File;
import java.util.concurrent.TimeUnit;
/**
* Provide a mechanism to invoke the replayer from outside.
*
* In particular, makes access from a host possible through {@code adb am startservice}.
*/
public class ReplayerService extends IntentService {
private static final String TAG = ReplayerService.class.getSimpleName();
private static final String EXTRA_FILENAME = "com.android.inputmethod.research.extra.FILENAME";
private static final long MAX_REPLAY_TIME = TimeUnit.SECONDS.toMillis(60);
public ReplayerService() {
super(ReplayerService.class.getSimpleName());
}
@Override
protected void onHandleIntent(final Intent intent) {
final String filename = intent.getStringExtra(EXTRA_FILENAME);
if (filename == null) return;
final ReplayData replayData = new MotionEventReader().readMotionEventData(
new File(filename));
synchronized (this) {
Replayer.getInstance().replay(replayData, new Runnable() {
@Override
public void run() {
synchronized (ReplayerService.this) {
ReplayerService.this.notify();
}
}
});
try {
wait(MAX_REPLAY_TIME);
} catch (InterruptedException e) {
Log.e(TAG, "Timeout while replaying.", e);
}
}
}
}

View File

@ -1,298 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.content.Context;
import android.util.JsonWriter;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Logs the use of the LatinIME keyboard.
*
* This class logs operations on the IME keyboard, including what the user has typed. Data is
* written to a {@link JsonWriter}, which will write to a local file.
*
* The JsonWriter is created on-demand by calling {@link #getInitializedJsonWriterLocked}.
*
* This class uses an executor to perform file-writing operations on a separate thread. It also
* tries to avoid creating unnecessary files if there is nothing to write. It also handles
* flushing, making sure it happens, but not too frequently.
*
* This functionality is off by default. See
* {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}.
*/
public class ResearchLog {
// TODO: Automatically initialize the JsonWriter rather than requiring the caller to manage it.
private static final String TAG = ResearchLog.class.getSimpleName();
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
private static final long FLUSH_DELAY_IN_MS = TimeUnit.SECONDS.toMillis(5);
/* package */ final ScheduledExecutorService mExecutor;
/* package */ final File mFile;
private final Context mContext;
// Earlier implementations used a dummy JsonWriter that just swallowed what it was given, but
// this was tricky to do well, because JsonWriter throws an exception if it is passed more than
// one top-level object.
private JsonWriter mJsonWriter = null;
// true if at least one byte of data has been written out to the log file. This must be
// remembered because JsonWriter requires that calls matching calls to beginObject and
// endObject, as well as beginArray and endArray, and the file is opened lazily, only when
// it is certain that data will be written. Alternatively, the matching call exceptions
// could be caught, but this might suppress other errors.
private boolean mHasWrittenData = false;
public ResearchLog(final File outputFile, final Context context) {
mExecutor = Executors.newSingleThreadScheduledExecutor();
mFile = outputFile;
mContext = context;
}
/**
* Returns true if this is a FeedbackLog.
*
* FeedbackLogs record only the data associated with a Feedback dialog. Instead of normal
* logging, they contain a LogStatement with the complete feedback string and optionally a
* recording of the user's supplied demo of the problem.
*/
public boolean isFeedbackLog() {
return false;
}
/**
* Waits for any publication requests to finish and closes the {@link JsonWriter} used for
* output.
*
* See class comment for details about {@code JsonWriter} construction.
*
* @param onClosed run after the close() operation has completed asynchronously
*/
private synchronized void close(final Runnable onClosed) {
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
if (mJsonWriter == null) return null;
// TODO: This is necessary to avoid an exception. Better would be to not even
// open the JsonWriter if the file is not even opened unless there is valid data
// to write.
if (!mHasWrittenData) {
mJsonWriter.beginArray();
}
mJsonWriter.endArray();
mHasWrittenData = false;
mJsonWriter.flush();
mJsonWriter.close();
if (DEBUG) {
Log.d(TAG, "closed " + mFile);
}
} catch (final Exception e) {
Log.d(TAG, "error when closing ResearchLog:", e);
} finally {
// Marking the file as read-only signals that this log file is ready to be
// uploaded.
if (mFile != null && mFile.exists()) {
mFile.setWritable(false, false);
}
if (onClosed != null) {
onClosed.run();
}
}
return null;
}
});
removeAnyScheduledFlush();
mExecutor.shutdown();
}
/**
* Block until the research log has shut down and spooled out all output or {@code timeout}
* occurs.
*
* @param timeout time to wait for close in milliseconds
*/
public void blockingClose(final long timeout) {
close(null);
awaitTermination(timeout, TimeUnit.MILLISECONDS);
}
/**
* Waits for publication requests to finish, closes the JsonWriter, but then deletes the backing
* output file.
*
* @param onAbort run after the abort() operation has completed asynchronously
*/
private synchronized void abort(final Runnable onAbort) {
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
if (mJsonWriter == null) return null;
if (mHasWrittenData) {
// TODO: This is necessary to avoid an exception. Better would be to not
// even open the JsonWriter if the file is not even opened unless there is
// valid data to write.
if (!mHasWrittenData) {
mJsonWriter.beginArray();
}
mJsonWriter.endArray();
mJsonWriter.close();
mHasWrittenData = false;
}
} finally {
if (mFile != null) {
mFile.delete();
}
if (onAbort != null) {
onAbort.run();
}
}
return null;
}
});
removeAnyScheduledFlush();
mExecutor.shutdown();
}
/**
* Block until the research log has aborted or {@code timeout} occurs.
*
* @param timeout time to wait for close in milliseconds
*/
public void blockingAbort(final long timeout) {
abort(null);
awaitTermination(timeout, TimeUnit.MILLISECONDS);
}
@UsedForTesting
public void awaitTermination(final long delay, final TimeUnit timeUnit) {
try {
if (!mExecutor.awaitTermination(delay, timeUnit)) {
Log.e(TAG, "ResearchLog executor timed out while awaiting terminaion");
}
} catch (final InterruptedException e) {
Log.e(TAG, "ResearchLog executor interrupted while awaiting terminaion", e);
}
}
/* package */ synchronized void flush() {
removeAnyScheduledFlush();
mExecutor.submit(mFlushCallable);
}
private final Callable<Object> mFlushCallable = new Callable<Object>() {
@Override
public Object call() throws Exception {
if (mJsonWriter != null) mJsonWriter.flush();
return null;
}
};
private ScheduledFuture<Object> mFlushFuture;
private void removeAnyScheduledFlush() {
if (mFlushFuture != null) {
mFlushFuture.cancel(false);
mFlushFuture = null;
}
}
private void scheduleFlush() {
removeAnyScheduledFlush();
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
}
/**
* Queues up {@code logUnit} to be published in the background.
*
* @param logUnit the {@link LogUnit} to be published
* @param canIncludePrivateData whether private data in the LogUnit should be included
*/
public synchronized void publish(final LogUnit logUnit, final boolean canIncludePrivateData) {
try {
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
logUnit.publishTo(ResearchLog.this, canIncludePrivateData);
scheduleFlush();
return null;
}
});
} catch (final RejectedExecutionException e) {
// TODO: Add code to record loss of data, and report.
if (DEBUG) {
Log.d(TAG, "ResearchLog.publish() rejecting scheduled execution", e);
}
}
}
/**
* Return a JsonWriter for this ResearchLog. It is initialized the first time this method is
* called. The cached value is returned in future calls.
*
* @throws IOException if opening the JsonWriter is not possible
*/
public JsonWriter getInitializedJsonWriterLocked() throws IOException {
if (mJsonWriter != null) return mJsonWriter;
if (mFile == null) throw new FileNotFoundException();
try {
final JsonWriter jsonWriter = createJsonWriter(mContext, mFile);
if (jsonWriter == null) throw new IOException("Could not create JsonWriter");
jsonWriter.beginArray();
mJsonWriter = jsonWriter;
mHasWrittenData = true;
return mJsonWriter;
} catch (final IOException e) {
if (DEBUG) {
Log.w(TAG, "Exception when creating JsonWriter", e);
Log.w(TAG, "Closing JsonWriter");
}
if (mJsonWriter != null) mJsonWriter.close();
mJsonWriter = null;
throw e;
}
}
/**
* Create the JsonWriter to write the ResearchLog to.
*
* This method may be overriden in testing to redirect the output.
*/
/* package for test */ JsonWriter createJsonWriter(final Context context, final File file)
throws IOException {
return new JsonWriter(new BufferedWriter(new OutputStreamWriter(
context.openFileOutput(file.getName(), Context.MODE_PRIVATE))));
}
}

View File

@ -1,113 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.io.FileFilter;
/**
* Manages log files.
*
* This class handles all aspects where and how research log data is stored. This includes
* generating log filenames in the correct place with the correct names, and cleaning up log files
* under this directory.
*/
public class ResearchLogDirectory {
public static final String TAG = ResearchLogDirectory.class.getSimpleName();
/* package */ static final String LOG_FILENAME_PREFIX = "researchLog";
private static final String FILENAME_SUFFIX = ".txt";
private static final String USER_RECORDING_FILENAME_PREFIX = "recording";
private static final ReadOnlyLogFileFilter sUploadableLogFileFilter =
new ReadOnlyLogFileFilter();
private final File mFilesDir;
static class ReadOnlyLogFileFilter implements FileFilter {
@Override
public boolean accept(final File pathname) {
return pathname.getName().startsWith(ResearchLogDirectory.LOG_FILENAME_PREFIX)
&& !pathname.canWrite();
}
}
/**
* Creates a new ResearchLogDirectory, creating the storage directory if it does not exist.
*/
public ResearchLogDirectory(final Context context) {
mFilesDir = getLoggingDirectory(context);
if (mFilesDir == null) {
throw new NullPointerException("No files directory specified");
}
if (!mFilesDir.exists()) {
mFilesDir.mkdirs();
}
}
private File getLoggingDirectory(final Context context) {
// TODO: Switch to using a subdirectory of getFilesDir().
return context.getFilesDir();
}
/**
* Get an array of log files that are ready for uploading.
*
* A file is ready for uploading if it is marked as read-only.
*
* @return the array of uploadable files
*/
public File[] getUploadableLogFiles() {
try {
return mFilesDir.listFiles(sUploadableLogFileFilter);
} catch (final SecurityException e) {
Log.e(TAG, "Could not cleanup log directory, permission denied", e);
return new File[0];
}
}
public void cleanupLogFilesOlderThan(final long time) {
try {
for (final File file : mFilesDir.listFiles()) {
final String filename = file.getName();
if ((filename.startsWith(LOG_FILENAME_PREFIX)
|| filename.startsWith(USER_RECORDING_FILENAME_PREFIX))
&& (file.lastModified() < time)) {
file.delete();
}
}
} catch (final SecurityException e) {
Log.e(TAG, "Could not cleanup log directory, permission denied", e);
}
}
public File getLogFilePath(final long time, final long nanoTime) {
return new File(mFilesDir, getUniqueFilename(LOG_FILENAME_PREFIX, time, nanoTime));
}
public File getUserRecordingFilePath(final long time, final long nanoTime) {
return new File(mFilesDir, getUniqueFilename(USER_RECORDING_FILENAME_PREFIX, time,
nanoTime));
}
private static String getUniqueFilename(final String prefix, final long time,
final long nanoTime) {
return prefix + "-" + time + "-" + nanoTime + FILENAME_SUFFIX;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.content.SharedPreferences;
import java.util.UUID;
public final class ResearchSettings {
public static final String PREF_RESEARCH_LOGGER_UUID = "pref_research_logger_uuid";
public static final String PREF_RESEARCH_LOGGER_ENABLED_FLAG =
"pref_research_logger_enabled_flag";
public static final String PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH =
"pref_research_logger_has_seen_splash";
public static final String PREF_RESEARCH_LAST_DIR_CLEANUP_TIME =
"pref_research_last_dir_cleanup_time";
private ResearchSettings() {
// Intentional empty constructor for singleton.
}
public static String readResearchLoggerUuid(final SharedPreferences prefs) {
if (prefs.contains(PREF_RESEARCH_LOGGER_UUID)) {
return prefs.getString(PREF_RESEARCH_LOGGER_UUID, null);
}
// Generate a random string as uuid if not yet set
final String newUuid = UUID.randomUUID().toString();
prefs.edit().putString(PREF_RESEARCH_LOGGER_UUID, newUuid).apply();
return newUuid;
}
public static boolean readResearchLoggerEnabledFlag(final SharedPreferences prefs) {
return prefs.getBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, false);
}
public static void writeResearchLoggerEnabledFlag(final SharedPreferences prefs,
final boolean isEnabled) {
prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, isEnabled).apply();
}
public static boolean readHasSeenSplash(final SharedPreferences prefs) {
return prefs.getBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, false);
}
public static void writeHasSeenSplash(final SharedPreferences prefs,
final boolean hasSeenSplash) {
prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, hasSeenSplash).apply();
}
public static long readResearchLastDirCleanupTime(final SharedPreferences prefs) {
return prefs.getLong(PREF_RESEARCH_LAST_DIR_CLEANUP_TIME, 0L);
}
public static void writeResearchLastDirCleanupTime(final SharedPreferences prefs,
final long lastDirCleanupTime) {
prefs.edit().putLong(PREF_RESEARCH_LAST_DIR_CLEANUP_TIME, lastDirCleanupTime).apply();
}
}

View File

@ -1,279 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.research;
import android.util.Log;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.util.concurrent.TimeUnit;
public class Statistics {
private static final String TAG = Statistics.class.getSimpleName();
private static final boolean DEBUG = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
// TODO: Cleanup comments to only including those giving meaningful information.
// Number of characters entered during a typing session
int mCharCount;
// Number of letter characters entered during a typing session
int mLetterCount;
// Number of number characters entered
int mNumberCount;
// Number of space characters entered
int mSpaceCount;
// Number of delete operations entered (taps on the backspace key)
int mDeleteKeyCount;
// Number of words entered during a session.
int mWordCount;
// Number of words found in the dictionary.
int mDictionaryWordCount;
// Number of words split and spaces automatically entered.
int mSplitWordsCount;
// Number of words entered during a session.
int mCorrectedWordsCount;
// Number of gestures that were input.
int mGesturesInputCount;
// Number of gestures that were deleted.
int mGesturesDeletedCount;
// Total number of characters in words entered by gesture.
int mGesturesCharsCount;
// Number of manual suggestions chosen.
int mManualSuggestionsCount;
// Number of times that autocorrection was invoked.
int mAutoCorrectionsCount;
// Number of times a commit was reverted in this session.
int mRevertCommitsCount;
// Whether the text field was empty upon editing
boolean mIsEmptyUponStarting;
boolean mIsEmptinessStateKnown;
// Counts of how often an n-gram is collected or not, and the reasons for the decision.
// Keep consistent with publishability result code list in MainLogBuffer
int mPublishableCount;
int mUnpublishableStoppingCount;
int mUnpublishableIncorrectWordCount;
int mUnpublishableSampledTooRecently;
int mUnpublishableDictionaryUnavailable;
int mUnpublishableMayContainDigit;
int mUnpublishableNotInDictionary;
// Timers to count average time to enter a key, first press a delete key,
// between delete keys, and then to return typing after a delete key.
final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
static class AverageTimeCounter {
int mCount;
int mTotalTime;
public void reset() {
mCount = 0;
mTotalTime = 0;
}
public void add(long deltaTime) {
mCount++;
mTotalTime += deltaTime;
}
public int getAverageTime() {
if (mCount == 0) {
return 0;
}
return mTotalTime / mCount;
}
}
// To account for the interruptions when the user's attention is directed elsewhere, times
// longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2);
public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10);
// The last time that a tap was performed
private long mLastTapTime;
// The type of the last keypress (delete key or not)
boolean mIsLastKeyDeleteKey;
private static final Statistics sInstance = new Statistics();
public static Statistics getInstance() {
return sInstance;
}
private Statistics() {
reset();
}
public void reset() {
mCharCount = 0;
mLetterCount = 0;
mNumberCount = 0;
mSpaceCount = 0;
mDeleteKeyCount = 0;
mWordCount = 0;
mDictionaryWordCount = 0;
mSplitWordsCount = 0;
mCorrectedWordsCount = 0;
mGesturesInputCount = 0;
mGesturesDeletedCount = 0;
mManualSuggestionsCount = 0;
mRevertCommitsCount = 0;
mAutoCorrectionsCount = 0;
mIsEmptyUponStarting = true;
mIsEmptinessStateKnown = false;
mKeyCounter.reset();
mBeforeDeleteKeyCounter.reset();
mDuringRepeatedDeleteKeysCounter.reset();
mAfterDeleteKeyCounter.reset();
mGesturesCharsCount = 0;
mGesturesDeletedCount = 0;
mPublishableCount = 0;
mUnpublishableStoppingCount = 0;
mUnpublishableIncorrectWordCount = 0;
mUnpublishableSampledTooRecently = 0;
mUnpublishableDictionaryUnavailable = 0;
mUnpublishableMayContainDigit = 0;
mUnpublishableNotInDictionary = 0;
mLastTapTime = 0;
mIsLastKeyDeleteKey = false;
}
public void recordChar(int codePoint, long time) {
if (DEBUG) {
Log.d(TAG, "recordChar() called");
}
if (codePoint == Constants.CODE_DELETE) {
mDeleteKeyCount++;
recordUserAction(time, true /* isDeletion */);
} else {
mCharCount++;
if (Character.isDigit(codePoint)) {
mNumberCount++;
}
if (Character.isLetter(codePoint)) {
mLetterCount++;
}
if (Character.isSpaceChar(codePoint)) {
mSpaceCount++;
}
recordUserAction(time, false /* isDeletion */);
}
}
public void recordWordEntered(final boolean isDictionaryWord,
final boolean containsCorrection) {
mWordCount++;
if (isDictionaryWord) {
mDictionaryWordCount++;
}
if (containsCorrection) {
mCorrectedWordsCount++;
}
}
public void recordSplitWords() {
mSplitWordsCount++;
}
public void recordGestureInput(final int numCharsEntered, final long time) {
mGesturesInputCount++;
mGesturesCharsCount += numCharsEntered;
recordUserAction(time, false /* isDeletion */);
}
public void setIsEmptyUponStarting(final boolean isEmpty) {
mIsEmptyUponStarting = isEmpty;
mIsEmptinessStateKnown = true;
}
public void recordGestureDelete(final int length, final long time) {
mGesturesDeletedCount++;
recordUserAction(time, true /* isDeletion */);
}
public void recordManualSuggestion(final long time) {
mManualSuggestionsCount++;
recordUserAction(time, false /* isDeletion */);
}
public void recordAutoCorrection(final long time) {
mAutoCorrectionsCount++;
recordUserAction(time, false /* isDeletion */);
}
public void recordRevertCommit(final long time) {
mRevertCommitsCount++;
recordUserAction(time, true /* isDeletion */);
}
private void recordUserAction(final long time, final boolean isDeletion) {
final long delta = time - mLastTapTime;
if (isDeletion) {
if (delta < MIN_DELETION_INTERMISSION) {
if (mIsLastKeyDeleteKey) {
mDuringRepeatedDeleteKeysCounter.add(delta);
} else {
mBeforeDeleteKeyCounter.add(delta);
}
} else {
ResearchLogger.onUserPause(delta);
}
} else {
if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
mAfterDeleteKeyCounter.add(delta);
} else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
mKeyCounter.add(delta);
} else {
ResearchLogger.onUserPause(delta);
}
}
mIsLastKeyDeleteKey = isDeletion;
mLastTapTime = time;
}
public void recordPublishabilityResultCode(final int publishabilityResultCode) {
// Keep consistent with publishability result code list in MainLogBuffer
switch (publishabilityResultCode) {
case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE:
mPublishableCount++;
break;
case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING:
mUnpublishableStoppingCount++;
break;
case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT:
mUnpublishableIncorrectWordCount++;
break;
case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY:
mUnpublishableSampledTooRecently++;
break;
case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE:
mUnpublishableDictionaryUnavailable++;
break;
case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT:
mUnpublishableMayContainDigit++;
break;
case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY:
mUnpublishableNotInDictionary++;
break;
}
}
}

Some files were not shown because too many files have changed in this diff Show More