aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java6
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java18
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecorator.java24
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java42
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java3
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java3
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java8
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java41
-rw-r--r--tests/src/com/android/inputmethod/latin/utils/ImportantNoticeUtilsTests.java222
13 files changed, 334 insertions, 60 deletions
diff --git a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
index c937eeeaa..5af31795c 100644
--- a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
@@ -84,8 +84,8 @@ public final class CursorAnchorInfoCompatWrapper {
}
@UsedForTesting
- public static boolean isAvailable() {
- return sCursorAnchorInfoClass.exists();
+ public boolean isAvailable() {
+ return sCursorAnchorInfoClass.exists() && mInstance != null;
}
private Object mInstance;
@@ -96,7 +96,7 @@ public final class CursorAnchorInfoCompatWrapper {
@UsedForTesting
public static CursorAnchorInfoCompatWrapper fromObject(final Object instance) {
- if (!isAvailable()) {
+ if (!sCursorAnchorInfoClass.exists()) {
return new CursorAnchorInfoCompatWrapper(null);
}
return new CursorAnchorInfoCompatWrapper(instance);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
index 75cc7d4cb..3dbbc9b9b 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DownloadManagerWrapper.java
@@ -54,15 +54,13 @@ public class DownloadManagerWrapper {
if (null != mDownloadManager) {
mDownloadManager.remove(ids);
}
+ } catch (IllegalArgumentException e) {
+ // This is expected to happen on boot when the device is encrypted.
} catch (SQLiteException e) {
// We couldn't remove the file from DownloadManager. Apparently, the database can't
// be opened. It may be a problem with file system corruption. In any case, there is
// not much we can do apart from avoiding crashing.
Log.e(TAG, "Can't remove files with ID " + ids + " from download manager", e);
- } catch (IllegalArgumentException e) {
- // Not sure how this can happen, but it could be another case where the provider
- // is disabled. Or it could be a bug in older versions of the framework.
- Log.e(TAG, "Can't find the content URL for DownloadManager?", e);
}
}
@@ -71,10 +69,10 @@ public class DownloadManagerWrapper {
if (null != mDownloadManager) {
return mDownloadManager.openDownloadedFile(fileId);
}
+ } catch (IllegalArgumentException e) {
+ // This is expected to happen on boot when the device is encrypted.
} catch (SQLiteException e) {
Log.e(TAG, "Can't open downloaded file with ID " + fileId, e);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Can't find the content URL for DownloadManager?", e);
}
// We come here if mDownloadManager is null or if an exception was thrown.
throw new FileNotFoundException();
@@ -85,10 +83,10 @@ public class DownloadManagerWrapper {
if (null != mDownloadManager) {
return mDownloadManager.query(query);
}
+ } catch (IllegalArgumentException e) {
+ // This is expected to happen on boot when the device is encrypted.
} catch (SQLiteException e) {
Log.e(TAG, "Can't query the download manager", e);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Can't find the content URL for DownloadManager?", e);
}
// We come here if mDownloadManager is null or if an exception was thrown.
return null;
@@ -99,10 +97,10 @@ public class DownloadManagerWrapper {
if (null != mDownloadManager) {
return mDownloadManager.enqueue(request);
}
+ } catch (IllegalArgumentException e) {
+ // This is expected to happen on boot when the device is encrypted.
} catch (SQLiteException e) {
Log.e(TAG, "Can't enqueue a request with the download manager", e);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Can't find the content URL for DownloadManager?", e);
}
return 0;
}
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6d0b2c2f1..2b16785c2 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -756,7 +756,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
public void onHideWindow() {
onDismissMoreKeysPanel();
final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
accessibilityDelegate.onHideWindow();
}
}
@@ -767,7 +768,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public boolean onHoverEvent(final MotionEvent event) {
final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
return accessibilityDelegate.onHoverEvent(event);
}
return super.onHoverEvent(event);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index a9d1239ed..841283b7f 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -105,7 +105,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
- if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
if (mAccessibilityDelegate == null) {
mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate(
this, mKeyDetector);
@@ -142,7 +142,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
mOriginY = y + container.getPaddingTop();
controller.onShowMoreKeysPanel(this);
final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
accessibilityDelegate.onShowMoreKeysKeyboard();
}
}
@@ -239,7 +240,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
return;
}
final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
accessibilityDelegate.onDismissMoreKeysKeyboard();
}
mController.onDismissMoreKeysPanel();
@@ -285,7 +287,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
@Override
public boolean onHoverEvent(final MotionEvent event) {
final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
return accessibilityDelegate.onHoverEvent(event);
}
return super.onHoverEvent(event);
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
index 315d36313..c22717f95 100644
--- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java
+++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
@@ -49,7 +49,7 @@ public class TextDecorator {
private int mMode = MODE_MONITOR;
private String mLastComposingText = null;
- private RectF mIndicatorBoundsForLastComposingText = new RectF();
+ private boolean mHasRtlCharsInLastComposingText = false;
private RectF mComposingTextBoundsForLastComposingText = new RectF();
private boolean mIsFullScreenMode = false;
@@ -182,7 +182,7 @@ public class TextDecorator {
private void layoutMain() {
final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper;
- if (info == null) {
+ if (info == null || !info.isAvailable()) {
cancelLayoutInternalExpectedly("CursorAnchorInfo isn't available.");
return;
}
@@ -241,20 +241,8 @@ public class TextDecorator {
right = Math.max(characterBounds.right, right);
}
mLastComposingText = composingTextString;
+ mHasRtlCharsInLastComposingText = useRtlLayout;
mComposingTextBoundsForLastComposingText.set(left, top, right, bottom);
- // The height and width of the indicator is the same as the height of the composing
- // text.
- final float indicatorSize = bottom - top;
- mIndicatorBoundsForLastComposingText.set(0.0f, 0.0f, indicatorSize, indicatorSize);
- // The horizontal position of the indicator depends on the text direction.
- final float indicatorTop = top;
- final float indicatorLeft;
- if (useRtlLayout) {
- indicatorLeft = left - indicatorSize;
- } else {
- indicatorLeft = right;
- }
- mIndicatorBoundsForLastComposingText.offset(indicatorLeft, indicatorTop);
}
final int selectionStart = info.getSelectionStart();
@@ -295,8 +283,8 @@ public class TextDecorator {
return;
}
- mUiOperator.layoutUi(matrix, mIndicatorBoundsForLastComposingText,
- mComposingTextBoundsForLastComposingText);
+ mUiOperator.layoutUi(matrix, mComposingTextBoundsForLastComposingText,
+ mHasRtlCharsInLastComposingText);
}
private void onClickIndicator() {
@@ -374,7 +362,7 @@ public class TextDecorator {
public void setOnClickListener(Runnable listener) {
}
@Override
- public void layoutUi(Matrix matrix, RectF indicatorBounds, RectF composingTextBounds) {
+ public void layoutUi(Matrix matrix, RectF composingTextBounds, boolean useRtlLayout) {
}
};
}
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java b/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java
index b67d17789..d87dc1bfa 100644
--- a/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java
+++ b/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java
@@ -26,6 +26,7 @@ import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.inputmethodservice.InputMethodService;
+import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@@ -50,6 +51,7 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator {
private final PopupWindow mTouchEventWindow;
private final View mTouchEventWindowClickListenerView;
private final float mHitAreaMarginInPixels;
+ private final RectF mDisplayRect;
/**
* This constructor is designed to be called from {@link InputMethodService#setInputView(View)}.
@@ -64,6 +66,9 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator {
R.integer.text_decorator_hit_area_margin_in_dp);
mHitAreaMarginInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
hitAreaMarginInDP, resources.getDisplayMetrics());
+ final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
+ mDisplayRect = new RectF(0.0f, 0.0f, displayMetrics.widthPixels,
+ displayMetrics.heightPixels);
mLocalRootView = new RelativeLayout(context);
mLocalRootView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
@@ -111,17 +116,40 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator {
mTouchEventWindow.dismiss();
}
+ private static final RectF getIndicatorBoundsInScreenCoordinates(final Matrix matrix,
+ final RectF composingTextBounds, final boolean showAtLeftSide) {
+ final float indicatorSize = composingTextBounds.height();
+ final RectF indicatorBounds;
+ if (showAtLeftSide) {
+ indicatorBounds = new RectF(composingTextBounds.left - indicatorSize,
+ composingTextBounds.top, composingTextBounds.left,
+ composingTextBounds.top + indicatorSize);
+ } else {
+ indicatorBounds = new RectF(composingTextBounds.right, composingTextBounds.top,
+ composingTextBounds.right + indicatorSize,
+ composingTextBounds.top + indicatorSize);
+ }
+ matrix.mapRect(indicatorBounds);
+ return indicatorBounds;
+ }
+
@Override
- public void layoutUi(final Matrix matrix, final RectF indicatorBounds,
- final RectF composingTextBounds) {
- final RectF indicatorBoundsInScreenCoordinates = new RectF();
- matrix.mapRect(indicatorBoundsInScreenCoordinates, indicatorBounds);
+ public void layoutUi(final Matrix matrix, final RectF composingTextBounds,
+ final boolean useRtlLayout) {
+ RectF indicatorBoundsInScreenCoordinates = getIndicatorBoundsInScreenCoordinates(matrix,
+ composingTextBounds, useRtlLayout /* showAtLeftSide */);
+ if (indicatorBoundsInScreenCoordinates.left < mDisplayRect.left ||
+ mDisplayRect.right < indicatorBoundsInScreenCoordinates.right) {
+ // The indicator is clipped by the screen. Show the indicator at the opposite side.
+ indicatorBoundsInScreenCoordinates = getIndicatorBoundsInScreenCoordinates(matrix,
+ composingTextBounds, !useRtlLayout /* showAtLeftSide */);
+ }
+
mAddToDictionaryIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
- final RectF hitAreaBounds = new RectF(composingTextBounds);
- hitAreaBounds.union(indicatorBounds);
final RectF hitAreaBoundsInScreenCoordinates = new RectF();
- matrix.mapRect(hitAreaBoundsInScreenCoordinates, hitAreaBounds);
+ matrix.mapRect(hitAreaBoundsInScreenCoordinates, composingTextBounds);
+ hitAreaBoundsInScreenCoordinates.union(indicatorBoundsInScreenCoordinates);
hitAreaBoundsInScreenCoordinates.inset(-mHitAreaMarginInPixels, -mHitAreaMarginInPixels);
final int[] originScreen = new int[2];
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java b/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java
index 9c0b64ad4..9e30e417e 100644
--- a/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java
+++ b/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.keyboard;
import android.graphics.Matrix;
-import android.graphics.PointF;
import android.graphics.RectF;
/**
@@ -45,9 +44,8 @@ public interface TextDecoratorUiOperator {
/**
* Called when the layout should be updated.
* @param matrix The matrix that transforms the local coordinates into the screen coordinates.
- * @param indicatorBounds The bounding box of the indicator, in local coordinates.
* @param composingTextBounds The bounding box of the composing text, in local coordinates.
+ * @param useRtlLayout {@code true} if the indicator should be optimized for RTL layout.
*/
- void layoutUi(final Matrix matrix, final RectF indicatorBounds,
- final RectF composingTextBounds);
+ void layoutUi(final Matrix matrix, final RectF composingTextBounds, final boolean useRtlLayout);
}
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
index 17dfc9cce..925ec6bfb 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
@@ -104,7 +104,8 @@ final class EmojiPageKeyboardView extends KeyboardView implements
public boolean onHoverEvent(final MotionEvent event) {
final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate =
mAccessibilityDelegate;
- if (accessibilityDelegate != null) {
+ if (accessibilityDelegate != null
+ && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
return accessibilityDelegate.onHoverEvent(event);
}
return super.onHoverEvent(event);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index ba7f967f1..d57db8e9a 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1033,7 +1033,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// with cursor movement when we have a hardware keyboard since we are not in charge.
final SettingsValues settingsValues = mSettings.getCurrent();
if ((!settingsValues.mHasHardwareKeyboard || ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED)
- && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd)) {
+ && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+ settingsValues)) {
mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
getCurrentRecapitalizeState());
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 21e2a1c10..fdab7f25f 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -358,10 +358,11 @@ public final class InputLogic {
* @param oldSelEnd old selection end
* @param newSelStart new selection start
* @param newSelEnd new selection end
+ * @param settingsValues the current values of the settings.
* @return whether the cursor has moved as a result of user interaction.
*/
public boolean onUpdateSelection(final int oldSelStart, final int oldSelEnd,
- final int newSelStart, final int newSelEnd) {
+ final int newSelStart, final int newSelEnd, final SettingsValues settingsValues) {
if (mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, oldSelEnd, newSelEnd)) {
return false;
}
@@ -386,8 +387,9 @@ public final class InputLogic {
// should be true, but that is if the framework had taken that wrong cursor position
// into account, which means we have to reset the entire composing state whenever there
// is or was a selection regardless of whether it changed or not.
- if (hasOrHadSelection || (selectionChangedOrSafeToReset
- && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
+ if (hasOrHadSelection || !settingsValues.needsToLookupSuggestions()
+ || (selectionChangedOrSafeToReset
+ && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
// If we are composing a word and moving the cursor, we would want to set a
// suggestion span for recorrection to work correctly. Unfortunately, that
// would involve the keyboard committing some new text, which would move the
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 33745a846..0fd5e139e 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -408,8 +408,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// Decided to be in the sliding suggestion mode only when the touch point has been moved
// upward. Further {@link MotionEvent}s will be delivered to
// {@link #onTouchEvent(MotionEvent)}.
- mNeedsToTransformTouchEventToHoverEvent = AccessibilityUtils.getInstance()
- .isTouchExplorationEnabled();
+ mNeedsToTransformTouchEventToHoverEvent =
+ AccessibilityUtils.getInstance().isTouchExplorationEnabled();
mIsDispatchingHoverEventToMoreSuggestions = false;
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index 8b7077879..ea406fa75 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -23,15 +23,24 @@ import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.R;
+import java.util.concurrent.TimeUnit;
+
public final class ImportantNoticeUtils {
private static final String TAG = ImportantNoticeUtils.class.getSimpleName();
// {@link SharedPreferences} name to save the last important notice version that has been
// displayed to users.
private static final String PREFERENCE_NAME = "important_notice_pref";
- private static final String KEY_IMPORTANT_NOTICE_VERSION = "important_notice_version";
+ @UsedForTesting
+ static final String KEY_IMPORTANT_NOTICE_VERSION = "important_notice_version";
+ @UsedForTesting
+ static final String KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE =
+ "timestamp_of_first_important_notice";
+ @UsedForTesting
+ static final long TIMEOUT_OF_IMPORTANT_NOTICE = TimeUnit.HOURS.toMillis(23);
public static final int VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS = 1;
// Copy of the hidden {@link Settings.Secure#USER_SETUP_COMPLETE} settings key.
@@ -56,15 +65,18 @@ public final class ImportantNoticeUtils {
}
}
- private static SharedPreferences getImportantNoticePreferences(final Context context) {
+ @UsedForTesting
+ static SharedPreferences getImportantNoticePreferences(final Context context) {
return context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
}
- private static int getCurrentImportantNoticeVersion(final Context context) {
+ @UsedForTesting
+ static int getCurrentImportantNoticeVersion(final Context context) {
return context.getResources().getInteger(R.integer.config_important_notice_version);
}
- private static int getLastImportantNoticeVersion(final Context context) {
+ @UsedForTesting
+ static int getLastImportantNoticeVersion(final Context context) {
return getImportantNoticePreferences(context).getInt(KEY_IMPORTANT_NOTICE_VERSION, 0);
}
@@ -77,6 +89,20 @@ public final class ImportantNoticeUtils {
return getCurrentImportantNoticeVersion(context) > lastVersion;
}
+ @UsedForTesting
+ static boolean hasTimeoutPassed(final Context context, final long currentTimeInMillis) {
+ final SharedPreferences prefs = getImportantNoticePreferences(context);
+ if (!prefs.contains(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE)) {
+ prefs.edit()
+ .putLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE, currentTimeInMillis)
+ .apply();
+ }
+ final long firstDisplayTimeInMillis = prefs.getLong(
+ KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE, currentTimeInMillis);
+ final long elapsedTime = currentTimeInMillis - firstDisplayTimeInMillis;
+ return elapsedTime >= TIMEOUT_OF_IMPORTANT_NOTICE;
+ }
+
public static boolean shouldShowImportantNotice(final Context context) {
if (!hasNewImportantNotice(context)) {
return false;
@@ -88,6 +114,10 @@ public final class ImportantNoticeUtils {
if (isInSystemSetupWizard(context)) {
return false;
}
+ if (hasTimeoutPassed(context, System.currentTimeMillis())) {
+ updateLastImportantNoticeVersion(context);
+ return false;
+ }
return true;
}
@@ -95,11 +125,12 @@ public final class ImportantNoticeUtils {
getImportantNoticePreferences(context)
.edit()
.putInt(KEY_IMPORTANT_NOTICE_VERSION, getNextImportantNoticeVersion(context))
+ .remove(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE)
.apply();
}
public static String getNextImportantNoticeTitle(final Context context) {
- final int nextVersion = getCurrentImportantNoticeVersion(context);
+ final int nextVersion = getNextImportantNoticeVersion(context);
final String[] importantNoticeTitleArray = context.getResources().getStringArray(
R.array.important_notice_title_array);
if (nextVersion > 0 && nextVersion < importantNoticeTitleArray.length) {
diff --git a/tests/src/com/android/inputmethod/latin/utils/ImportantNoticeUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/ImportantNoticeUtilsTests.java
new file mode 100644
index 000000000..819d76328
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/ImportantNoticeUtilsTests.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2014 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.latin.utils;
+
+import static com.android.inputmethod.latin.utils.ImportantNoticeUtils.KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE;
+import static com.android.inputmethod.latin.utils.ImportantNoticeUtils.KEY_IMPORTANT_NOTICE_VERSION;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+public class ImportantNoticeUtilsTests extends AndroidTestCase {
+ // This should be aligned with R.integer.config_important_notice_version.
+ private static final int CURRENT_IMPORTANT_NOTICE_VERSION = 1;
+
+ private ImportantNoticePreferences mImportantNoticePreferences;
+
+ private static class ImportantNoticePreferences {
+ private final SharedPreferences mPref;
+
+ private Integer mVersion;
+ private Long mLastTime;
+
+ public ImportantNoticePreferences(final Context context) {
+ mPref = ImportantNoticeUtils.getImportantNoticePreferences(context);
+ }
+
+ private Integer getInt(final String key) {
+ if (mPref.contains(key)) {
+ return mPref.getInt(key, 0);
+ }
+ return null;
+ }
+
+ public Long getLong(final String key) {
+ if (mPref.contains(key)) {
+ return mPref.getLong(key, 0);
+ }
+ return null;
+ }
+
+ private void putInt(final String key, final Integer value) {
+ if (value == null) {
+ removePreference(key);
+ } else {
+ mPref.edit().putInt(key, value).apply();
+ }
+ }
+
+ private void putLong(final String key, final Long value) {
+ if (value == null) {
+ removePreference(key);
+ } else {
+ mPref.edit().putLong(key, value).apply();
+ }
+ }
+
+ private void removePreference(final String key) {
+ mPref.edit().remove(key).apply();
+ }
+
+ public void save() {
+ mVersion = getInt(KEY_IMPORTANT_NOTICE_VERSION);
+ mLastTime = getLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE);
+ }
+
+ public void restore() {
+ putInt(KEY_IMPORTANT_NOTICE_VERSION, mVersion);
+ putLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE, mLastTime);
+ }
+
+ public void clear() {
+ removePreference(KEY_IMPORTANT_NOTICE_VERSION);
+ removePreference(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE);
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mImportantNoticePreferences = new ImportantNoticePreferences(getContext());
+ mImportantNoticePreferences.save();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mImportantNoticePreferences.restore();
+ }
+
+ public void testCurrentVersion() {
+ assertEquals("Current version", CURRENT_IMPORTANT_NOTICE_VERSION,
+ ImportantNoticeUtils.getCurrentImportantNoticeVersion(getContext()));
+ }
+
+ public void testUpdateVersion() {
+ mImportantNoticePreferences.clear();
+
+ assertEquals("Current boolean before update", true,
+ ImportantNoticeUtils.shouldShowImportantNotice(getContext()));
+ assertEquals("Last version before update", 0,
+ ImportantNoticeUtils.getLastImportantNoticeVersion(getContext()));
+ assertEquals("Next version before update ", 1,
+ ImportantNoticeUtils.getNextImportantNoticeVersion(getContext()));
+ assertEquals("Current title before update", false, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeTitle(getContext())));
+ assertEquals("Current contents before update", false, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeContents(getContext())));
+
+ ImportantNoticeUtils.updateLastImportantNoticeVersion(getContext());
+
+ assertEquals("Current boolean after update", false,
+ ImportantNoticeUtils.shouldShowImportantNotice(getContext()));
+ assertEquals("Last version after update", 1,
+ ImportantNoticeUtils.getLastImportantNoticeVersion(getContext()));
+ assertEquals("Next version after update", 2,
+ ImportantNoticeUtils.getNextImportantNoticeVersion(getContext()));
+ assertEquals("Current title after update", true, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeTitle(getContext())));
+ assertEquals("Current contents after update", true, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeContents(getContext())));
+ }
+
+ private static void sleep(final long millseconds) {
+ try { Thread.sleep(millseconds); } catch (final Exception e) { /* ignore */ }
+ }
+
+ public void testTimeout() {
+ final long lastTime = System.currentTimeMillis()
+ - ImportantNoticeUtils.TIMEOUT_OF_IMPORTANT_NOTICE
+ + TimeUnit.MILLISECONDS.toMillis(1000);
+ mImportantNoticePreferences.clear();
+ assertEquals("Before set last time", null,
+ mImportantNoticePreferences.getLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE));
+ assertEquals("Set last time", false,
+ ImportantNoticeUtils.hasTimeoutPassed(getContext(), lastTime));
+ assertEquals("After set last time", (Long)lastTime,
+ mImportantNoticePreferences.getLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE));
+
+ // Call {@link ImportantNoticeUtils#shouldShowImportantNotice(Context)} before timeout.
+ assertEquals("Current boolean before timeout 1", true,
+ ImportantNoticeUtils.shouldShowImportantNotice(getContext()));
+ assertEquals("Last version before timeout 1", 0,
+ ImportantNoticeUtils.getLastImportantNoticeVersion(getContext()));
+ assertEquals("Next version before timeout 1", 1,
+ ImportantNoticeUtils.getNextImportantNoticeVersion(getContext()));
+ assertEquals("Last time before timeout 1", (Long)lastTime,
+ mImportantNoticePreferences.getLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE));
+ assertEquals("Current title before timeout 1", false, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeTitle(getContext())));
+ assertEquals("Current contents before timeout 1", false, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeContents(getContext())));
+
+ sleep(TimeUnit.MILLISECONDS.toMillis(600));
+
+ // Call {@link ImportantNoticeUtils#shouldShowImportantNotice(Context)} before timeout
+ // again.
+ assertEquals("Current boolean before timeout 2", true,
+ ImportantNoticeUtils.shouldShowImportantNotice(getContext()));
+ assertEquals("Last version before timeout 2", 0,
+ ImportantNoticeUtils.getLastImportantNoticeVersion(getContext()));
+ assertEquals("Next version before timeout 2", 1,
+ ImportantNoticeUtils.getNextImportantNoticeVersion(getContext()));
+ assertEquals("Last time before timeout 2", (Long)lastTime,
+ mImportantNoticePreferences.getLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE));
+ assertEquals("Current title before timeout 2", false, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeTitle(getContext())));
+ assertEquals("Current contents before timeout 2", false, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeContents(getContext())));
+
+ sleep(TimeUnit.MILLISECONDS.toMillis(600));
+
+ // Call {@link ImportantNoticeUtils#shouldShowImportantNotice(Context)} after timeout.
+ assertEquals("Current boolean after timeout 1", false,
+ ImportantNoticeUtils.shouldShowImportantNotice(getContext()));
+ assertEquals("Last version after timeout 1", 1,
+ ImportantNoticeUtils.getLastImportantNoticeVersion(getContext()));
+ assertEquals("Next version after timeout 1", 2,
+ ImportantNoticeUtils.getNextImportantNoticeVersion(getContext()));
+ assertEquals("Last time aflter timeout 1", null,
+ mImportantNoticePreferences.getLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE));
+ assertEquals("Current title after timeout 1", true, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeTitle(getContext())));
+ assertEquals("Current contents after timeout 1", true, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeContents(getContext())));
+
+ sleep(TimeUnit.MILLISECONDS.toMillis(600));
+
+ // Call {@link ImportantNoticeUtils#shouldShowImportantNotice(Context)} after timeout again.
+ assertEquals("Current boolean after timeout 2", false,
+ ImportantNoticeUtils.shouldShowImportantNotice(getContext()));
+ assertEquals("Last version after timeout 2", 1,
+ ImportantNoticeUtils.getLastImportantNoticeVersion(getContext()));
+ assertEquals("Next version after timeout 2", 2,
+ ImportantNoticeUtils.getNextImportantNoticeVersion(getContext()));
+ assertEquals("Last time aflter timeout 2", null,
+ mImportantNoticePreferences.getLong(KEY_TIMESTAMP_OF_FIRST_IMPORTANT_NOTICE));
+ assertEquals("Current title after timeout 2", true, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeTitle(getContext())));
+ assertEquals("Current contents after timeout 2", true, TextUtils.isEmpty(
+ ImportantNoticeUtils.getNextImportantNoticeContents(getContext())));
+ }
+}