diff options
author | Jake Hamby <jhamby@google.com> | 2010-09-22 17:05:30 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2010-09-22 17:05:30 -0700 |
commit | 2695660674141025223240cf990bf7e5745a53a9 (patch) | |
tree | 6ac92ff23007056e1f09a8f85fded6777e836918 | |
parent | 4ad2f9b87047237538fdfbe4730b378b7aa3bbcc (diff) | |
parent | 5496db047df21a44280c56984dbc296e2a1d7968 (diff) | |
download | packages_apps_Settings-2695660674141025223240cf990bf7e5745a53a9.zip packages_apps_Settings-2695660674141025223240cf990bf7e5745a53a9.tar.gz packages_apps_Settings-2695660674141025223240cf990bf7e5745a53a9.tar.bz2 |
am 5496db04: am 59091c08: Fix Bluetooth device name max length checking.
Merge commit '5496db047df21a44280c56984dbc296e2a1d7968'
* commit '5496db047df21a44280c56984dbc296e2a1d7968':
Fix Bluetooth device name max length checking.
-rw-r--r-- | src/com/android/settings/bluetooth/BluetoothNamePreference.java | 74 | ||||
-rw-r--r-- | tests/AndroidManifest.xml | 5 | ||||
-rw-r--r-- | tests/src/com/android/settings/tests/SettingsLaunchPerformance.java (renamed from tests/src/com/android/settings/SettingsLaunchPerformance.java) | 2 | ||||
-rw-r--r-- | tests/src/com/android/settings/tests/Utf8ByteLengthFilterTest.java | 113 |
4 files changed, 189 insertions, 5 deletions
diff --git a/src/com/android/settings/bluetooth/BluetoothNamePreference.java b/src/com/android/settings/bluetooth/BluetoothNamePreference.java index 7a9a0c1..c99ab4c 100644 --- a/src/com/android/settings/bluetooth/BluetoothNamePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothNamePreference.java @@ -27,8 +27,8 @@ import android.content.IntentFilter; import android.preference.EditTextPreference; import android.text.Editable; import android.text.InputFilter; +import android.text.Spanned; import android.text.TextWatcher; -import android.text.InputFilter.LengthFilter; import android.util.AttributeSet; import android.widget.Button; import android.widget.EditText; @@ -40,8 +40,7 @@ import android.widget.EditText; */ public class BluetoothNamePreference extends EditTextPreference implements TextWatcher { private static final String TAG = "BluetoothNamePreference"; - // TODO(): Investigate bluetoothd/dbus crash when length is set to 248, limit as per spec. - private static final int BLUETOOTH_NAME_MAX_LENGTH = 200; + private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248; private LocalBluetoothManager mLocalManager; @@ -75,8 +74,11 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW // Make sure the OK button is disabled (if necessary) after rotation EditText et = getEditText(); - et.setFilters(new InputFilter[] {new LengthFilter(BLUETOOTH_NAME_MAX_LENGTH)}); if (et != null) { + et.setFilters(new InputFilter[] { + new Utf8ByteLengthFilter(BLUETOOTH_NAME_MAX_LENGTH_BYTES) + }); + et.addTextChangedListener(this); Dialog d = getDialog(); if (d instanceof AlertDialog) { @@ -136,4 +138,68 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW public void onTextChanged(CharSequence s, int start, int before, int count) { // not used } + + /** + * This filter will constrain edits so that the text length is not + * greater than the specified number of bytes using UTF-8 encoding. + * <p>The JNI method used by {@link android.server.BluetoothService} + * to convert UTF-16 to UTF-8 doesn't support surrogate pairs, + * therefore code points outside of the basic multilingual plane + * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters, + * rather than a single 4-byte UTF-8 encoding. Dalvik implements this + * conversion in {@code convertUtf16ToUtf8()} in + * {@code dalvik/vm/UtfString.c}. + * <p>This JNI method is unlikely to change in the future due to + * backwards compatibility requirements. It's also unclear whether + * the installed base of Bluetooth devices would correctly handle the + * encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6. + * However, this filter will still work in scenarios where surrogate + * pairs are encoded as 4 bytes, with the caveat that the maximum + * length will be constrained more conservatively than necessary. + */ + public static class Utf8ByteLengthFilter implements InputFilter { + private int mMaxBytes; + + public Utf8ByteLengthFilter(int maxBytes) { + mMaxBytes = maxBytes; + } + + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + int srcByteCount = 0; + // count UTF-8 bytes in source substring + for (int i = start; i < end; i++) { + char c = source.charAt(i); + srcByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); + } + int destLen = dest.length(); + int destByteCount = 0; + // count UTF-8 bytes in destination excluding replaced section + for (int i = 0; i < destLen; i++) { + if (i < dstart || i >= dend) { + char c = dest.charAt(i); + destByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); + } + } + int keepBytes = mMaxBytes - destByteCount; + if (keepBytes <= 0) { + return ""; + } else if (keepBytes >= srcByteCount) { + return null; // use original dest string + } else { + // find end position of largest sequence that fits in keepBytes + for (int i = start; i < end; i++) { + char c = source.charAt(i); + keepBytes -= (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); + if (keepBytes < 0) { + return source.subSequence(start, i); + } + } + // If the entire substring fits, we should have returned null + // above, so this line should not be reached. If for some + // reason it is, return null to use the original dest string. + return null; + } + } + } } diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index e125128..53bf40f 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -58,4 +58,9 @@ android:label="Settings Launch Performance"> </instrumentation> + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.settings" + android:label="Settings Test Cases"> + </instrumentation> + </manifest> diff --git a/tests/src/com/android/settings/SettingsLaunchPerformance.java b/tests/src/com/android/settings/tests/SettingsLaunchPerformance.java index 05154e2..225a60b 100644 --- a/tests/src/com/android/settings/SettingsLaunchPerformance.java +++ b/tests/src/com/android/settings/tests/SettingsLaunchPerformance.java @@ -26,7 +26,7 @@ import java.util.Map; * Instrumentation class for Settings launch performance testing. */ public class SettingsLaunchPerformance extends LaunchPerformanceBase { - + public static final String LOG_TAG = "SettingsLaunchPerformance"; public SettingsLaunchPerformance() { diff --git a/tests/src/com/android/settings/tests/Utf8ByteLengthFilterTest.java b/tests/src/com/android/settings/tests/Utf8ByteLengthFilterTest.java new file mode 100644 index 0000000..c03f9c0 --- /dev/null +++ b/tests/src/com/android/settings/tests/Utf8ByteLengthFilterTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2010 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.settings.tests; + +import android.test.AndroidTestCase; +import android.text.InputFilter; +import android.text.SpannableStringBuilder; + +import com.android.settings.bluetooth.BluetoothNamePreference; + +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargets; + +@TestTargetClass(BluetoothNamePreference.Utf8ByteLengthFilter.class) +public class Utf8ByteLengthFilterTest extends AndroidTestCase { + + @TestTargets({ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "filter", + args = {java.lang.CharSequence.class, int.class, int.class, android.text.Spanned.class, + int.class, int.class} + ), + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "BluetoothNamePreference.Utf8ByteLengthFilter", + args = {int.class} + ) + }) + public void testFilter() { + // Define the variables + CharSequence source; + SpannableStringBuilder dest; + // Constructor to create a LengthFilter + BluetoothNamePreference.Utf8ByteLengthFilter lengthFilter = new BluetoothNamePreference.Utf8ByteLengthFilter(10); + InputFilter[] filters = {lengthFilter}; + + // filter() implicitly invoked. If the total length > filter length, the filter will + // cut off the source CharSequence from beginning to fit the filter length. + source = "abc"; + dest = new SpannableStringBuilder("abcdefgh"); + dest.setFilters(filters); + + dest.insert(1, source); + String expectedString1 = "aabbcdefgh"; + assertEquals(expectedString1, dest.toString()); + + dest.replace(5, 8, source); + String expectedString2 = "aabbcabcgh"; + assertEquals(expectedString2, dest.toString()); + + dest.insert(2, source); + assertEquals(expectedString2, dest.toString()); + + dest.delete(1, 3); + String expectedString3 = "abcabcgh"; + assertEquals(expectedString3, dest.toString()); + + dest.append("12345"); + String expectedString4 = "abcabcgh12"; + assertEquals(expectedString4, dest.toString()); + + source = "\u60a8\u597d"; // 2 Chinese chars == 6 bytes in UTF-8 + dest.replace(8, 10, source); + assertEquals(expectedString3, dest.toString()); + + dest.replace(0, 1, source); + String expectedString5 = "\u60a8bcabcgh"; + assertEquals(expectedString5, dest.toString()); + + dest.replace(0, 4, source); + String expectedString6 = "\u60a8\u597dbcgh"; + assertEquals(expectedString6, dest.toString()); + + source = "\u00a3\u00a5"; // 2 Latin-1 chars == 4 bytes in UTF-8 + dest.delete(2, 6); + dest.insert(0, source); + String expectedString7 = "\u00a3\u00a5\u60a8\u597d"; + assertEquals(expectedString7, dest.toString()); + + dest.replace(2, 3, source); + String expectedString8 = "\u00a3\u00a5\u00a3\u597d"; + assertEquals(expectedString8, dest.toString()); + + dest.replace(3, 4, source); + String expectedString9 = "\u00a3\u00a5\u00a3\u00a3\u00a5"; + assertEquals(expectedString9, dest.toString()); + + // filter() explicitly invoked + dest = new SpannableStringBuilder("abcdefgh"); + CharSequence beforeFilterSource = "TestLengthFilter"; + String expectedAfterFilter = "TestLength"; + CharSequence actualAfterFilter = lengthFilter.filter(beforeFilterSource, 0, + beforeFilterSource.length(), dest, 0, dest.length()); + assertEquals(expectedAfterFilter, actualAfterFilter); + } +} |