17f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell/* 27f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * Copyright (C) 2011 The Android Open Source Project 37f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * 47f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * Licensed under the Apache License, Version 2.0 (the "License"); 57f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * you may not use this file except in compliance with the License. 67f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * You may obtain a copy of the License at 77f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * 87f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * http://www.apache.org/licenses/LICENSE-2.0 97f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * 107f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * Unless required by applicable law or agreed to in writing, software 117f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * distributed under the License is distributed on an "AS IS" BASIS, 127f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * See the License for the specific language governing permissions and 147f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * limitations under the License. 157f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell */ 167f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powellpackage android.text.method; 177f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 187f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powellimport android.content.Context; 197f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powellimport android.graphics.Rect; 20b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournaderimport android.icu.text.CaseMap; 21b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournaderimport android.icu.text.Edits; 22b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournaderimport android.text.SpannableStringBuilder; 23b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournaderimport android.text.Spanned; 247f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powellimport android.util.Log; 257f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powellimport android.view.View; 26eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonakaimport android.widget.TextView; 277f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 287f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powellimport java.util.Locale; 297f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 307f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell/** 317f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * Transforms source text into an ALL CAPS string, locale-aware. 327f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * 337f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell * @hide 347f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell */ 357f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powellpublic class AllCapsTransformationMethod implements TransformationMethod2 { 367f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell private static final String TAG = "AllCapsTransformationMethod"; 377f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 387f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell private boolean mEnabled; 397f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell private Locale mLocale; 407f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 417f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell public AllCapsTransformationMethod(Context context) { 42b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader mLocale = context.getResources().getConfiguration().getLocales().get(0); 437f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell } 447f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 457f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell @Override 467f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell public CharSequence getTransformation(CharSequence source, View view) { 47eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka if (!mEnabled) { 48eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka Log.w(TAG, "Caller did not enable length changes; not transforming text"); 49eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka return source; 507f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell } 51eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka 52eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka if (source == null) { 53eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka return null; 54eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka } 55eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka 56eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka Locale locale = null; 57eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka if (view instanceof TextView) { 58eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka locale = ((TextView)view).getTextLocale(); 59eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka } 60eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka if (locale == null) { 61eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka locale = mLocale; 62eaddec8c7037e40f68863fe2bc9bc95401b284c0Seigo Nonaka } 63b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader 64b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader if (!(source instanceof Spanned)) { // No spans 65b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader return CaseMap.toUpper().apply( 66b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader locale, source, new StringBuilder(), 67b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader null /* we don't need the edits */); 68b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader } 69b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader 70b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final Edits edits = new Edits(); 71b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final SpannableStringBuilder result = CaseMap.toUpper().apply( 72b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader locale, source, new SpannableStringBuilder(), edits); 73b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader if (!edits.hasChanges()) { 74b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // No changes happened while capitalizing. We can return the source as it was. 75b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader return source; 76b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader } 77b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader 78b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final Edits.Iterator iterator = edits.getFineIterator(); 79b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final Spanned spanned = (Spanned) source; 80b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final int sourceLength = source.length(); 81b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final Object[] spans = spanned.getSpans(0, sourceLength, Object.class); 82b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader for (Object span : spans) { 83b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final int sourceStart = spanned.getSpanStart(span); 84b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final int sourceEnd = spanned.getSpanEnd(span); 85b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final int flags = spanned.getSpanFlags(span); 86b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // Make sure the indexes are not at the end of the string, since in that case 87b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // iterator.findSourceIndex() would fail. 88b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final int destStart = sourceStart == sourceLength ? result.length() : 89b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader mapToDest(iterator, sourceStart); 90b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader final int destEnd = sourceEnd == sourceLength ? result.length() : 91b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader mapToDest(iterator, sourceEnd); 92b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader result.setSpan(span, destStart, destEnd, flags); 93b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader } 94b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader return result; 95b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader } 96b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader 97b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader private static int mapToDest(Edits.Iterator iterator, int sourceIndex) { 98b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // Guaranteed to succeed if sourceIndex < source.length(). 99b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader iterator.findSourceIndex(sourceIndex); 100b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader if (sourceIndex == iterator.sourceIndex()) { 101b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader return iterator.destinationIndex(); 102b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader } 103b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // We handle the situation differently depending on if we are in the changed slice or an 104b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // unchanged one: In an unchanged slice, we can find the exact location the span 105b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // boundary was before and map there. 106b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // 107b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // But in a changed slice, we need to treat the whole destination slice as an atomic unit. 108b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // We adjust the span boundary to the end of that slice to reduce of the chance of adjacent 109b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // spans in the source overlapping in the result. (The choice for the end vs the beginning 110b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // is somewhat arbitrary, but was taken because we except to see slightly more spans only 111b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // affecting a base character compared to spans only affecting a combining character.) 112b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader if (iterator.hasChange()) { 113b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader return iterator.destinationIndex() + iterator.newLength(); 114b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader } else { 115b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader // Move the index 1:1 along with this unchanged piece of text. 116b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader return iterator.destinationIndex() + (sourceIndex - iterator.sourceIndex()); 117b2d00340fd7b2e8301e7d85e1225c9aa68dd3440Roozbeh Pournader } 1187f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell } 1197f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 1207f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell @Override 1217f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction, 1227f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell Rect previouslyFocusedRect) { 1237f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell } 1247f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 1257f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell @Override 1267f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell public void setLengthChangesAllowed(boolean allowLengthChanges) { 1277f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell mEnabled = allowLengthChanges; 1287f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell } 1297f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell 1307f8f79a1ff086c04a3ad2a442b1d39a8186e3e50Adam Powell} 131