Merge "Remove researcher logger"
commit
262b1e7518
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 */);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))));
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue