1/* 2 * Copyright (C) 2016 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 com.example.android.supportv4.view.inputmethod; 18 19import android.app.Activity; 20import android.graphics.Color; 21import android.net.Uri; 22import android.os.Bundle; 23import android.os.Parcelable; 24import android.text.TextUtils; 25import android.util.Log; 26import android.view.inputmethod.EditorInfo; 27import android.view.inputmethod.InputConnection; 28import android.webkit.WebView; 29import android.widget.EditText; 30import android.widget.LinearLayout; 31import android.widget.TextView; 32 33import androidx.core.view.inputmethod.EditorInfoCompat; 34import androidx.core.view.inputmethod.InputConnectionCompat; 35import androidx.core.view.inputmethod.InputContentInfoCompat; 36 37import com.example.android.supportv4.R; 38 39import java.util.ArrayList; 40import java.util.Arrays; 41 42/** 43 * Demo activity for using {@link InputConnectionCompat}. 44 */ 45public class CommitContentSupport extends Activity { 46 private static final String INPUT_CONTENT_INFO_KEY = "COMMIT_CONTENT_INPUT_CONTENT_INFO"; 47 private static final String COMMIT_CONTENT_FLAGS_KEY = "COMMIT_CONTENT_FLAGS"; 48 private static final String TAG = "CommitContentSupport"; 49 50 private WebView mWebView; 51 private TextView mLabel; 52 private TextView mContentUri; 53 private TextView mLinkUri; 54 private TextView mMimeTypes; 55 private TextView mFlags; 56 57 private InputContentInfoCompat mCurrentInputContentInfo; 58 private int mCurrentFlags; 59 60 @Override 61 public void onCreate(Bundle savedInstanceState) { 62 super.onCreate(savedInstanceState); 63 64 setContentView(R.layout.commit_content); 65 66 final LinearLayout layout = 67 findViewById(R.id.commit_content_sample_edit_boxes); 68 69 // This declares that the IME cannot commit any content with 70 // InputConnectionCompat#commitContent(). 71 layout.addView(createEditTextWithContentMimeTypes(null)); 72 73 // This declares that the IME can commit contents with 74 // InputConnectionCompat#commitContent() if they match "image/gif". 75 layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/gif"})); 76 77 // This declares that the IME can commit contents with 78 // InputConnectionCompat#commitContent() if they match "image/png". 79 layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/png"})); 80 81 // This declares that the IME can commit contents with 82 // InputConnectionCompat#commitContent() if they match "image/jpeg". 83 layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/jpeg"})); 84 85 // This declares that the IME can commit contents with 86 // InputConnectionCompat#commitContent() if they match "image/webp". 87 layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/webp"})); 88 89 // This declares that the IME can commit contents with 90 // InputConnectionCompat#commitContent() if they match "image/png", "image/gif", 91 // "image/jpeg", or "image/webp". 92 layout.addView(createEditTextWithContentMimeTypes( 93 new String[]{"image/png", "image/gif", "image/jpeg", "image/webp"})); 94 95 mWebView = findViewById(R.id.commit_content_webview); 96 mMimeTypes = findViewById(R.id.text_commit_content_mime_types); 97 mLabel = findViewById(R.id.text_commit_content_label); 98 mContentUri = findViewById(R.id.text_commit_content_content_uri); 99 mLinkUri = findViewById(R.id.text_commit_content_link_uri); 100 mFlags = findViewById(R.id.text_commit_content_link_flags); 101 102 if (savedInstanceState != null) { 103 final InputContentInfoCompat previousInputContentInfo = InputContentInfoCompat.wrap( 104 savedInstanceState.getParcelable(INPUT_CONTENT_INFO_KEY)); 105 final int previousFlags = savedInstanceState.getInt(COMMIT_CONTENT_FLAGS_KEY); 106 if (previousInputContentInfo != null) { 107 onCommitContentInternal(previousInputContentInfo, previousFlags); 108 } 109 } 110 } 111 112 private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, 113 String[] contentMimeTypes) { 114 // Clear the temporary permission (if any). See below about why we do this here. 115 try { 116 if (mCurrentInputContentInfo != null) { 117 mCurrentInputContentInfo.releasePermission(); 118 } 119 } catch (Exception e) { 120 Log.e(TAG, "InputContentInfoCompat#releasePermission() failed.", e); 121 } finally { 122 mCurrentInputContentInfo = null; 123 } 124 125 mWebView.loadUrl("about:blank"); 126 mMimeTypes.setText(""); 127 mContentUri.setText(""); 128 mLabel.setText(""); 129 mLinkUri.setText(""); 130 mFlags.setText(""); 131 132 boolean supported = false; 133 for (final String mimeType : contentMimeTypes) { 134 if (inputContentInfo.getDescription().hasMimeType(mimeType)) { 135 supported = true; 136 break; 137 } 138 } 139 if (!supported) { 140 return false; 141 } 142 143 return onCommitContentInternal(inputContentInfo, flags); 144 } 145 146 private boolean onCommitContentInternal(InputContentInfoCompat inputContentInfo, int flags) { 147 if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { 148 try { 149 inputContentInfo.requestPermission(); 150 } catch (Exception e) { 151 Log.e(TAG, "InputContentInfoCompat#requestPermission() failed.", e); 152 return false; 153 } 154 } 155 156 mMimeTypes.setText( 157 Arrays.toString(inputContentInfo.getDescription().filterMimeTypes("*/*"))); 158 mContentUri.setText(inputContentInfo.getContentUri().toString()); 159 mLabel.setText(inputContentInfo.getDescription().getLabel()); 160 Uri linkUri = inputContentInfo.getLinkUri(); 161 mLinkUri.setText(linkUri != null ? linkUri.toString() : "null"); 162 mFlags.setText(flagsToString(flags)); 163 mWebView.loadUrl(inputContentInfo.getContentUri().toString()); 164 mWebView.setBackgroundColor(Color.TRANSPARENT); 165 166 // Due to the asynchronous nature of WebView, it is a bit too early to call 167 // inputContentInfo.releasePermission() here. Hence we call IC#releasePermission() when this 168 // method is called next time. Note that calling IC#releasePermission() is just to be a 169 // good citizen. Even if we failed to call that method, the system would eventually revoke 170 // the permission sometime after inputContentInfo object gets garbage-collected. 171 mCurrentInputContentInfo = inputContentInfo; 172 mCurrentFlags = flags; 173 174 return true; 175 } 176 177 @Override 178 public void onSaveInstanceState(Bundle savedInstanceState) { 179 if (mCurrentInputContentInfo != null) { 180 savedInstanceState.putParcelable(INPUT_CONTENT_INFO_KEY, 181 (Parcelable) mCurrentInputContentInfo.unwrap()); 182 savedInstanceState.putInt(COMMIT_CONTENT_FLAGS_KEY, mCurrentFlags); 183 } 184 mCurrentInputContentInfo = null; 185 mCurrentFlags = 0; 186 super.onSaveInstanceState(savedInstanceState); 187 } 188 189 /** 190 * Creates a new instance of {@link EditText} that is configured to specify the given content 191 * MIME types to {@link EditorInfo#contentMimeTypes} so that developers 192 * can locally test how the current input method behaves for such content MIME types. 193 * 194 * @param contentMimeTypes A {@link String} array that indicates the supported content MIME 195 * types 196 * @return a new instance of {@link EditText}, which specifies 197 * {@link EditorInfo#contentMimeTypes} with the given content 198 * MIME types 199 */ 200 private EditText createEditTextWithContentMimeTypes(String[] contentMimeTypes) { 201 final CharSequence hintText; 202 final String[] mimeTypes; // our own copy of contentMimeTypes. 203 if (contentMimeTypes == null || contentMimeTypes.length == 0) { 204 hintText = "MIME: []"; 205 mimeTypes = new String[0]; 206 } else { 207 hintText = "MIME: " + Arrays.toString(contentMimeTypes); 208 mimeTypes = Arrays.copyOf(contentMimeTypes, contentMimeTypes.length); 209 } 210 EditText exitText = new EditText(this) { 211 @Override 212 public InputConnection onCreateInputConnection(EditorInfo editorInfo) { 213 final InputConnection ic = super.onCreateInputConnection(editorInfo); 214 EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes); 215 final InputConnectionCompat.OnCommitContentListener callback = 216 (inputContentInfo, flags, opts) -> 217 CommitContentSupport.this.onCommitContent( 218 inputContentInfo, flags, mimeTypes); 219 return InputConnectionCompat.createWrapper(ic, editorInfo, callback); 220 } 221 }; 222 exitText.setHint(hintText); 223 exitText.setTextColor(Color.WHITE); 224 exitText.setHintTextColor(Color.WHITE); 225 return exitText; 226 } 227 228 /** 229 * Converts {@code flags} specified in {@link InputConnectionCompat#commitContent( 230 *InputConnection, EditorInfo, InputContentInfoCompat, int, Bundle)} to a human readable 231 * string. 232 * 233 * @param flags the 2nd parameter of 234 * {@link InputConnectionCompat#commitContent(InputConnection, EditorInfo, 235 * InputContentInfoCompat, int, Bundle)} 236 * @return a human readable string that corresponds to the given {@code flags} 237 */ 238 private static String flagsToString(int flags) { 239 final ArrayList<String> tokens = new ArrayList<>(); 240 if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { 241 tokens.add("INPUT_CONTENT_GRANT_READ_URI_PERMISSION"); 242 flags &= ~InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION; 243 } 244 if (flags != 0) { 245 tokens.add("0x" + Integer.toHexString(flags)); 246 } 247 return TextUtils.join(" | ", tokens); 248 } 249 250} 251