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