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