1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.emoji.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.inputmethodservice.InputMethodService; 22import android.os.Build; 23import android.text.InputType; 24import android.util.AttributeSet; 25import android.view.LayoutInflater; 26import android.view.View; 27import android.view.ViewGroup; 28import android.view.inputmethod.EditorInfo; 29import android.view.inputmethod.InputConnection; 30import android.widget.LinearLayout; 31 32import androidx.annotation.NonNull; 33import androidx.annotation.Nullable; 34import androidx.annotation.RequiresApi; 35import androidx.emoji.R; 36import androidx.emoji.text.EmojiCompat; 37import androidx.emoji.text.EmojiSpan; 38 39/** 40 * Layout that contains emoji compatibility enhanced ExtractEditText. Should be used by 41 * {@link InputMethodService} implementations. 42 * <p/> 43 * Call {@link #onUpdateExtractingViews(InputMethodService, EditorInfo)} from 44 * {@link InputMethodService#onUpdateExtractingViews(EditorInfo) 45 * InputMethodService#onUpdateExtractingViews(EditorInfo)}. 46 * <pre> 47 * public class MyInputMethodService extends InputMethodService { 48 * // .. 49 * {@literal @}Override 50 * public View onCreateExtractTextView() { 51 * mExtractView = getLayoutInflater().inflate(R.layout.emoji_input_method_extract_layout, 52 * null); 53 * return mExtractView; 54 * } 55 * 56 * {@literal @}Override 57 * public void onUpdateExtractingViews(EditorInfo ei) { 58 * mExtractView.onUpdateExtractingViews(this, ei); 59 * } 60 * } 61 * </pre> 62 * 63 * @attr ref androidx.emoji.R.styleable#EmojiExtractTextLayout_emojiReplaceStrategy 64 */ 65public class EmojiExtractTextLayout extends LinearLayout { 66 67 private ExtractButtonCompat mExtractAction; 68 private EmojiExtractEditText mExtractEditText; 69 private ViewGroup mExtractAccessories; 70 private View.OnClickListener mButtonOnClickListener; 71 72 /** 73 * Prevent calling {@link #init(Context, AttributeSet, int)}} multiple times in case super() 74 * constructors call other constructors. 75 */ 76 private boolean mInitialized; 77 78 public EmojiExtractTextLayout(Context context) { 79 super(context); 80 init(context, null /*attrs*/, 0 /*defStyleAttr*/, 0 /*defStyleRes*/); 81 } 82 83 public EmojiExtractTextLayout(Context context, 84 @Nullable AttributeSet attrs) { 85 super(context, attrs); 86 init(context, attrs, 0 /*defStyleAttr*/, 0 /*defStyleRes*/); 87 } 88 89 public EmojiExtractTextLayout(Context context, 90 @Nullable AttributeSet attrs, int defStyleAttr) { 91 super(context, attrs, defStyleAttr); 92 init(context, attrs, defStyleAttr, 0 /*defStyleRes*/); 93 } 94 95 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 96 public EmojiExtractTextLayout(Context context, AttributeSet attrs, 97 int defStyleAttr, int defStyleRes) { 98 super(context, attrs, defStyleAttr, defStyleRes); 99 init(context, attrs, defStyleAttr, defStyleRes); 100 } 101 102 private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, 103 int defStyleRes) { 104 if (!mInitialized) { 105 mInitialized = true; 106 setOrientation(HORIZONTAL); 107 final View view = LayoutInflater.from(context) 108 .inflate(R.layout.input_method_extract_view, this /*root*/, 109 true /*attachToRoot*/); 110 mExtractAccessories = view.findViewById(R.id.inputExtractAccessories); 111 mExtractAction = view.findViewById(R.id.inputExtractAction); 112 mExtractEditText = view.findViewById(android.R.id.inputExtractEditText); 113 114 if (attrs != null) { 115 final TypedArray a = context.obtainStyledAttributes(attrs, 116 R.styleable.EmojiExtractTextLayout, defStyleAttr, defStyleRes); 117 final int replaceStrategy = a.getInteger( 118 R.styleable.EmojiExtractTextLayout_emojiReplaceStrategy, 119 EmojiCompat.REPLACE_STRATEGY_DEFAULT); 120 mExtractEditText.setEmojiReplaceStrategy(replaceStrategy); 121 a.recycle(); 122 } 123 } 124 } 125 126 /** 127 * Sets whether to replace all emoji with {@link EmojiSpan}s. Default value is 128 * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}. 129 * 130 * @param replaceStrategy should be one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}, 131 * {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT}, 132 * {@link EmojiCompat#REPLACE_STRATEGY_ALL} 133 * 134 * @attr ref androidx.emoji.R.styleable#EmojiExtractTextLayout_emojiReplaceStrategy 135 */ 136 public void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) { 137 mExtractEditText.setEmojiReplaceStrategy(replaceStrategy); 138 } 139 140 /** 141 * Returns whether to replace all emoji with {@link EmojiSpan}s. Default value is 142 * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}. 143 * 144 * @return one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}, 145 * {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT}, 146 * {@link EmojiCompat#REPLACE_STRATEGY_ALL} 147 * 148 * @attr ref androidx.emoji.R.styleable#EmojiExtractTextLayout_emojiReplaceStrategy 149 */ 150 public int getEmojiReplaceStrategy() { 151 return mExtractEditText.getEmojiReplaceStrategy(); 152 } 153 154 /** 155 * Initializes the layout. Call this function from 156 * {@link InputMethodService#onUpdateExtractingViews(EditorInfo) 157 * InputMethodService#onUpdateExtractingViews(EditorInfo)}. 158 */ 159 public void onUpdateExtractingViews(InputMethodService inputMethodService, EditorInfo ei) { 160 // the following code is ported as it is from InputMethodService.onUpdateExtractingViews 161 if (!inputMethodService.isExtractViewShown()) { 162 return; 163 } 164 165 if (mExtractAccessories == null) { 166 return; 167 } 168 169 final boolean hasAction = ei.actionLabel != null 170 || ((ei.imeOptions & EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE 171 && (ei.imeOptions & EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION) == 0 172 && ei.inputType != InputType.TYPE_NULL); 173 174 if (hasAction) { 175 mExtractAccessories.setVisibility(View.VISIBLE); 176 if (mExtractAction != null) { 177 if (ei.actionLabel != null) { 178 mExtractAction.setText(ei.actionLabel); 179 } else { 180 mExtractAction.setText(inputMethodService.getTextForImeAction(ei.imeOptions)); 181 } 182 mExtractAction.setOnClickListener(getButtonClickListener(inputMethodService)); 183 } 184 } else { 185 mExtractAccessories.setVisibility(View.GONE); 186 if (mExtractAction != null) { 187 mExtractAction.setOnClickListener(null); 188 } 189 } 190 } 191 192 private View.OnClickListener getButtonClickListener( 193 final InputMethodService inputMethodService) { 194 if (mButtonOnClickListener == null) { 195 mButtonOnClickListener = new ButtonOnclickListener(inputMethodService); 196 } 197 return mButtonOnClickListener; 198 } 199 200 private static final class ButtonOnclickListener implements View.OnClickListener { 201 private final InputMethodService mInputMethodService; 202 203 ButtonOnclickListener(InputMethodService inputMethodService) { 204 mInputMethodService = inputMethodService; 205 } 206 207 /** 208 * The following code is ported as it is from InputMethodService.mActionClickListener. 209 */ 210 @Override 211 public void onClick(View v) { 212 final EditorInfo ei = mInputMethodService.getCurrentInputEditorInfo(); 213 final InputConnection ic = mInputMethodService.getCurrentInputConnection(); 214 if (ei != null && ic != null) { 215 if (ei.actionId != 0) { 216 ic.performEditorAction(ei.actionId); 217 } else if ((ei.imeOptions & EditorInfo.IME_MASK_ACTION) 218 != EditorInfo.IME_ACTION_NONE) { 219 ic.performEditorAction(ei.imeOptions & EditorInfo.IME_MASK_ACTION); 220 } 221 } 222 } 223 } 224} 225