add research log ui control

- lets users flag a particular time in the research log
- lets users delete the log for this session

also makes the UsabilityLog setting control whether the ResearchLog logs or not.

multi-project commit with I89067e7d3b8daca7179333f1dbe82224c26920fe

Bug: 6188932
Change-Id: I89864ef3ab53b0efe1ea8d75247be08712f0c399
main
Kurt Partridge 2012-06-04 12:27:37 -07:00
parent f739119f3f
commit 724bc479f7
7 changed files with 177 additions and 33 deletions

View File

@ -26,6 +26,8 @@
<string name="english_ime_settings">Android keyboard settings</string> <string name="english_ime_settings">Android keyboard settings</string>
<!-- Title for Latin keyboard input options dialog [CHAR LIMIT=25] --> <!-- Title for Latin keyboard input options dialog [CHAR LIMIT=25] -->
<string name="english_ime_input_options">Input options</string> <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=25] -->
<string name="english_ime_research_log">Research Log Commands</string>
<!-- Name of Android spell checker service --> <!-- Name of Android spell checker service -->
<string name="spell_checker_service_name">Android spell checker</string> <string name="spell_checker_service_name">Android spell checker</string>
@ -233,6 +235,20 @@
<!-- Title for input language selection screen --> <!-- Title for input language selection screen -->
<string name="language_selection_title">Input languages</string> <string name="language_selection_title">Input languages</string>
<!-- Title for dialog option that lets user mark a particular time in the log for later review by experts [CHAR LIMIT=25] -->
<string name="note_timestamp_for_researchlog">Note timestamp in log</string>
<!-- Toast notification message that the time has been marked for later review. [CHAR LIMIT=25] -->
<string name="notify_recorded_timestamp">Recorded timestamp</string>
<!-- Title for dialog option to let users cancel logging and delete log for this session [CHAR LIMIT=25] -->
<string name="do_not_log_this_session">Do not log this session</string>
<!-- Toast notification that the system is processing the request to delete the log for this session [CHAR LIMIT=25] -->
<string name="notify_session_log_deleting">Deleting session log</string>
<!-- Toast notification that the system has successfully deleted the log for this session [CHAR LIMIT=25] -->
<string name="notify_session_log_deleted">Session log deleted</string>
<!-- Toast notification that the system has failed to delete the log for this session [CHAR LIMIT=25] -->
<string name="notify_session_log_not_deleted">Session log NOT deleted</string>
<!-- Preference for input language selection --> <!-- Preference for input language selection -->
<string name="select_language">Input languages</string> <string name="select_language">Input languages</string>

View File

@ -22,23 +22,8 @@
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
> >
<!-- Base key style for the key which may have settings or tab key as popup key. --> <!-- Base key style for the key which may have settings or tab key as popup key. -->
<switch> <include
<case latin:keyboardLayout="@xml/key_styles_f1" />
latin:clobberSettingsKey="true"
>
<key-style
latin:styleName="f1MoreKeysStyle"
latin:backgroundType="functional" />
</case>
<!-- clobberSettingsKey="false" -->
<default>
<key-style
latin:styleName="f1MoreKeysStyle"
latin:keyLabelFlags="hasPopupHint"
latin:moreKeys="!text/settings_as_more_key"
latin:backgroundType="functional" />
</default>
</switch>
<!-- Functional key styles --> <!-- Functional key styles -->
<switch> <switch>
<case <case

View File

@ -0,0 +1,43 @@
<?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.
*/
-->
<merge
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
>
<!-- Base key style for the key which may have settings or tab key as popup key. -->
<!-- Kept as a separate file for cleaner overriding by an overlay. -->
<switch>
<case
latin:clobberSettingsKey="true"
>
<key-style
latin:styleName="f1MoreKeysStyle"
latin:backgroundType="functional" />
</case>
<!-- clobberSettingsKey="false" -->
<default>
<key-style
latin:styleName="f1MoreKeysStyle"
latin:keyLabelFlags="hasPopupHint"
latin:moreKeys="!text/settings_as_more_key"
latin:backgroundType="functional" />
</default>
</switch>
</merge>

View File

@ -89,7 +89,8 @@ public class Keyboard {
private static final int MINIMUM_LETTER_CODE = CODE_TAB; private static final int MINIMUM_LETTER_CODE = CODE_TAB;
/** Special keys code. Must be negative. /** Special keys code. Must be negative.
* These should be aligned with values/keycodes.xml * These should be aligned with KeyboardCodesSet.ID_TO_NAME[],
* KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[]
*/ */
public static final int CODE_SHIFT = -1; public static final int CODE_SHIFT = -1;
public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
@ -101,8 +102,9 @@ public class Keyboard {
public static final int CODE_ACTION_NEXT = -8; public static final int CODE_ACTION_NEXT = -8;
public static final int CODE_ACTION_PREVIOUS = -9; public static final int CODE_ACTION_PREVIOUS = -9;
public static final int CODE_LANGUAGE_SWITCH = -10; public static final int CODE_LANGUAGE_SWITCH = -10;
public static final int CODE_RESEARCH = -11;
// Code value representing the code is not specified. // Code value representing the code is not specified.
public static final int CODE_UNSPECIFIED = -11; public static final int CODE_UNSPECIFIED = -12;
public final KeyboardId mId; public final KeyboardId mId;
public final int mThemeId; public final int mThemeId;

View File

@ -52,6 +52,7 @@ public class KeyboardCodesSet {
"key_action_next", "key_action_next",
"key_action_previous", "key_action_previous",
"key_language_switch", "key_language_switch",
"key_research",
"key_unspecified", "key_unspecified",
"key_left_parenthesis", "key_left_parenthesis",
"key_right_parenthesis", "key_right_parenthesis",
@ -86,6 +87,7 @@ public class KeyboardCodesSet {
Keyboard.CODE_ACTION_NEXT, Keyboard.CODE_ACTION_NEXT,
Keyboard.CODE_ACTION_PREVIOUS, Keyboard.CODE_ACTION_PREVIOUS,
Keyboard.CODE_LANGUAGE_SWITCH, Keyboard.CODE_LANGUAGE_SWITCH,
Keyboard.CODE_RESEARCH,
Keyboard.CODE_UNSPECIFIED, Keyboard.CODE_UNSPECIFIED,
CODE_LEFT_PARENTHESIS, CODE_LEFT_PARENTHESIS,
CODE_RIGHT_PARENTHESIS, CODE_RIGHT_PARENTHESIS,
@ -112,6 +114,7 @@ public class KeyboardCodesSet {
DEFAULT[11], DEFAULT[11],
DEFAULT[12], DEFAULT[12],
DEFAULT[13], DEFAULT[13],
DEFAULT[14],
CODE_RIGHT_PARENTHESIS, CODE_RIGHT_PARENTHESIS,
CODE_LEFT_PARENTHESIS, CODE_LEFT_PARENTHESIS,
CODE_GREATER_THAN_SIGN, CODE_GREATER_THAN_SIGN,

View File

@ -1330,6 +1330,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case Keyboard.CODE_LANGUAGE_SWITCH: case Keyboard.CODE_LANGUAGE_SWITCH:
handleLanguageSwitchKey(); handleLanguageSwitchKey();
break; break;
case Keyboard.CODE_RESEARCH:
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.getInstance().presentResearchDialog(this);
}
break;
default: default:
if (primaryCode == Keyboard.CODE_TAB if (primaryCode == Keyboard.CODE_TAB
&& mInputAttributes.mEditorAction == EditorInfo.IME_ACTION_NEXT) { && mInputAttributes.mEditorAction == EditorInfo.IME_ACTION_NEXT) {
@ -2444,10 +2449,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final AlertDialog.Builder builder = new AlertDialog.Builder(this) final AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setItems(items, listener) .setItems(items, listener)
.setTitle(title); .setTitle(title);
showOptionDialogInternal(builder.create()); showOptionDialog(builder.create());
} }
private void showOptionDialogInternal(AlertDialog dialog) { /* package */ void showOptionDialog(AlertDialog dialog) {
final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken(); final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken();
if (windowToken == null) return; if (windowToken == null) return;

View File

@ -18,6 +18,8 @@ package com.android.inputmethod.latin;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService;
@ -33,9 +35,9 @@ import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.widget.Toast;
import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@ -134,12 +136,16 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
if (prefs != null) { if (prefs != null) {
sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
prefs.registerOnSharedPreferenceChangeListener(sInstance); prefs.registerOnSharedPreferenceChangeListener(this);
} }
} }
public synchronized void start() { public synchronized void start() {
Log.d(TAG, "start called"); Log.d(TAG, "start called");
if (!sIsLogging) {
// Log.w(TAG, "not in usability mode; not logging");
return;
}
if (mFilesDir == null || !mFilesDir.exists()) { if (mFilesDir == null || !mFilesDir.exists()) {
Log.w(TAG, "IME storage directory does not exist. Cannot start logging."); Log.w(TAG, "IME storage directory does not exist. Cannot start logging.");
} else { } else {
@ -192,16 +198,17 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
e1.printStackTrace(); e1.printStackTrace();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} } finally {
mJsonWriter = NULL_JSON_WRITER; mJsonWriter = NULL_JSON_WRITER;
mFile = null; mFile = null;
mLoggingState = LOGGING_STATE_OFF; mLoggingState = LOGGING_STATE_OFF;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "logfile closed"); Log.d(TAG, "logfile closed");
} }
Log.d(TAG, "finished stop(), notifying"); Log.d(TAG, "finished stop(), notifying");
synchronized (ResearchLogger.this) { synchronized (ResearchLogger.this) {
ResearchLogger.this.notify(); ResearchLogger.this.notify();
}
} }
} }
}); });
@ -213,6 +220,38 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
} }
} }
public synchronized boolean abort() {
Log.d(TAG, "abort called");
boolean isLogFileDeleted = false;
if (mLoggingHandler != null && mLoggingState == LOGGING_STATE_ON) {
mLoggingState = LOGGING_STATE_STOPPING;
try {
Log.d(TAG, "closing jsonwriter");
mJsonWriter.endArray();
mJsonWriter.close();
} catch (IllegalStateException e1) {
// assume that this is just the json not being terminated properly.
// ignore
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
mJsonWriter = NULL_JSON_WRITER;
// delete file
final boolean isDeleted = mFile.delete();
if (isDeleted) {
isLogFileDeleted = true;
}
mFile = null;
mLoggingState = LOGGING_STATE_OFF;
if (DEBUG) {
Log.d(TAG, "logfile closed");
}
}
}
return isLogFileDeleted;
}
/* package */ synchronized void flush() { /* package */ synchronized void flush() {
try { try {
mJsonWriter.flush(); mJsonWriter.flush();
@ -227,6 +266,50 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return; return;
} }
sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
if (sIsLogging == false) {
abort();
}
}
/* package */ void presentResearchDialog(final LatinIME latinIME) {
final CharSequence title = latinIME.getString(R.string.english_ime_research_log);
final CharSequence[] items = new CharSequence[] {
latinIME.getString(R.string.note_timestamp_for_researchlog),
latinIME.getString(R.string.do_not_log_this_session),
};
final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface di, int position) {
di.dismiss();
switch (position) {
case 0:
ResearchLogger.getInstance().userTimestamp();
Toast.makeText(latinIME, R.string.notify_recorded_timestamp,
Toast.LENGTH_LONG).show();
break;
case 1:
Toast toast = Toast.makeText(latinIME,
R.string.notify_session_log_deleting, Toast.LENGTH_LONG);
toast.show();
final ResearchLogger logger = ResearchLogger.getInstance();
boolean isLogDeleted = logger.abort();
toast.cancel();
if (isLogDeleted) {
Toast.makeText(latinIME, R.string.notify_session_log_deleted,
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(latinIME,
R.string.notify_session_log_not_deleted, Toast.LENGTH_LONG)
.show();
}
break;
}
}
};
final AlertDialog.Builder builder = new AlertDialog.Builder(latinIME)
.setItems(items, listener)
.setTitle(title);
latinIME.showOptionDialog(builder.create());
} }
private static final String CURRENT_TIME_KEY = "_ct"; private static final String CURRENT_TIME_KEY = "_ct";
@ -756,4 +839,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
getInstance().writeEvent(EVENTKEYS_SUGGESTIONSVIEW_SETSUGGESTIONS, values); getInstance().writeEvent(EVENTKEYS_SUGGESTIONSVIEW_SETSUGGESTIONS, values);
} }
} }
private static final String[] EVENTKEYS_USER_TIMESTAMP = {
"UserTimestamp"
};
public void userTimestamp() {
getInstance().writeEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES);
}
} }