1/*
2 * Copyright (C) 2009 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.latin;
18
19import com.android.inputmethod.voice.SettingsUtil;
20
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.SharedPreferences;
24import android.preference.PreferenceManager;
25import android.view.inputmethod.InputConnection;
26
27import java.util.Calendar;
28import java.util.HashMap;
29import java.util.Map;
30
31/**
32 * Logic to determine when to display hints on usage to the user.
33 */
34public class Hints {
35    public interface Display {
36        public void showHint(int viewResource);
37    }
38
39    private static final String TAG = "Hints";
40    private static final String PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN =
41            "voice_hint_num_unique_days_shown";
42    private static final String PREF_VOICE_HINT_LAST_TIME_SHOWN =
43            "voice_hint_last_time_shown";
44    private static final String PREF_VOICE_INPUT_LAST_TIME_USED =
45            "voice_input_last_time_used";
46    private static final String PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT =
47            "voice_punctuation_hint_view_count";
48    private static final int DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW = 7;
49    private static final int DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS = 7;
50
51    private Context mContext;
52    private Display mDisplay;
53    private boolean mVoiceResultContainedPunctuation;
54    private int mSwipeHintMaxDaysToShow;
55    private int mPunctuationHintMaxDisplays;
56
57    // Only show punctuation hint if voice result did not contain punctuation.
58    static final Map<CharSequence, String> SPEAKABLE_PUNCTUATION
59            = new HashMap<CharSequence, String>();
60    static {
61        SPEAKABLE_PUNCTUATION.put(",", "comma");
62        SPEAKABLE_PUNCTUATION.put(".", "period");
63        SPEAKABLE_PUNCTUATION.put("?", "question mark");
64    }
65
66    public Hints(Context context, Display display) {
67        mContext = context;
68        mDisplay = display;
69
70        ContentResolver cr = mContext.getContentResolver();
71        mSwipeHintMaxDaysToShow = SettingsUtil.getSettingsInt(
72                cr,
73                SettingsUtil.LATIN_IME_VOICE_INPUT_SWIPE_HINT_MAX_DAYS,
74                DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW);
75        mPunctuationHintMaxDisplays = SettingsUtil.getSettingsInt(
76                cr,
77                SettingsUtil.LATIN_IME_VOICE_INPUT_PUNCTUATION_HINT_MAX_DISPLAYS,
78                DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS);
79    }
80
81    public boolean showSwipeHintIfNecessary(boolean fieldRecommended) {
82        if (fieldRecommended && shouldShowSwipeHint()) {
83            showHint(R.layout.voice_swipe_hint);
84            return true;
85        }
86
87        return false;
88    }
89
90    public boolean showPunctuationHintIfNecessary(InputConnection ic) {
91        if (!mVoiceResultContainedPunctuation
92                && ic != null
93                && getAndIncrementPref(PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT)
94                        < mPunctuationHintMaxDisplays) {
95            CharSequence charBeforeCursor = ic.getTextBeforeCursor(1, 0);
96            if (SPEAKABLE_PUNCTUATION.containsKey(charBeforeCursor)) {
97                showHint(R.layout.voice_punctuation_hint);
98                return true;
99            }
100        }
101
102        return false;
103    }
104
105    public void registerVoiceResult(String text) {
106        // Update the current time as the last time voice input was used.
107        SharedPreferences.Editor editor =
108                PreferenceManager.getDefaultSharedPreferences(mContext).edit();
109        editor.putLong(PREF_VOICE_INPUT_LAST_TIME_USED, System.currentTimeMillis());
110        editor.commit();
111
112        mVoiceResultContainedPunctuation = false;
113        for (CharSequence s : SPEAKABLE_PUNCTUATION.keySet()) {
114            if (text.indexOf(s.toString()) >= 0) {
115                mVoiceResultContainedPunctuation = true;
116                break;
117            }
118        }
119    }
120
121    private boolean shouldShowSwipeHint() {
122        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
123
124        int numUniqueDaysShown = sp.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0);
125
126        // If we've already shown the hint for enough days, we'll return false.
127        if (numUniqueDaysShown < mSwipeHintMaxDaysToShow) {
128
129            long lastTimeVoiceWasUsed = sp.getLong(PREF_VOICE_INPUT_LAST_TIME_USED, 0);
130
131            // If the user has used voice today, we'll return false. (We don't show the hint on
132            // any day that the user has already used voice.)
133            if (!isFromToday(lastTimeVoiceWasUsed)) {
134                return true;
135            }
136        }
137
138        return false;
139    }
140
141    /**
142     * Determines whether the provided time is from some time today (i.e., this day, month,
143     * and year).
144     */
145    private boolean isFromToday(long timeInMillis) {
146        if (timeInMillis == 0) return false;
147
148        Calendar today = Calendar.getInstance();
149        today.setTimeInMillis(System.currentTimeMillis());
150
151        Calendar timestamp = Calendar.getInstance();
152        timestamp.setTimeInMillis(timeInMillis);
153
154        return (today.get(Calendar.YEAR) == timestamp.get(Calendar.YEAR) &&
155                today.get(Calendar.DAY_OF_MONTH) == timestamp.get(Calendar.DAY_OF_MONTH) &&
156                today.get(Calendar.MONTH) == timestamp.get(Calendar.MONTH));
157    }
158
159    private void showHint(int hintViewResource) {
160        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
161
162        int numUniqueDaysShown = sp.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0);
163        long lastTimeHintWasShown = sp.getLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, 0);
164
165        // If this is the first time the hint is being shown today, increase the saved values
166        // to represent that. We don't need to increase the last time the hint was shown unless
167        // it is a different day from the current value.
168        if (!isFromToday(lastTimeHintWasShown)) {
169            SharedPreferences.Editor editor = sp.edit();
170            editor.putInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, numUniqueDaysShown + 1);
171            editor.putLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, System.currentTimeMillis());
172            editor.commit();
173        }
174
175        if (mDisplay != null) {
176            mDisplay.showHint(hintViewResource);
177        }
178    }
179
180    private int getAndIncrementPref(String pref) {
181        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
182        int value = sp.getInt(pref, 0);
183        SharedPreferences.Editor editor = sp.edit();
184        editor.putInt(pref, value + 1);
185        editor.commit();
186        return value;
187    }
188}
189