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