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