/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.example.android.commitcontent.app; import android.support.v13.view.inputmethod.EditorInfoCompat; import android.support.v13.view.inputmethod.InputConnectionCompat; import android.support.v13.view.inputmethod.InputContentInfoCompat; import android.app.Activity; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.webkit.WebView; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import java.util.ArrayList; import java.util.Arrays; public class MainActivity extends Activity { private static final String INPUT_CONTENT_INFO_KEY = "COMMIT_CONTENT_INPUT_CONTENT_INFO"; private static final String COMMIT_CONTENT_FLAGS_KEY = "COMMIT_CONTENT_FLAGS"; private static String TAG = "CommitContentSupport"; private WebView mWebView; private TextView mLabel; private TextView mContentUri; private TextView mLinkUri; private TextView mMimeTypes; private TextView mFlags; private InputContentInfoCompat mCurrentInputContentInfo; private int mCurrentFlags; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.commit_content); final LinearLayout layout = (LinearLayout) findViewById(R.id.commit_content_sample_edit_boxes); // This declares that the IME cannot commit any content with // InputConnectionCompat#commitContent(). layout.addView(createEditTextWithContentMimeTypes(null)); // This declares that the IME can commit contents with // InputConnectionCompat#commitContent() if they match "image/gif". layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/gif"})); // This declares that the IME can commit contents with // InputConnectionCompat#commitContent() if they match "image/png". layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/png"})); // This declares that the IME can commit contents with // InputConnectionCompat#commitContent() if they match "image/jpeg". layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/jpeg"})); // This declares that the IME can commit contents with // InputConnectionCompat#commitContent() if they match "image/webp". layout.addView(createEditTextWithContentMimeTypes(new String[]{"image/webp"})); // This declares that the IME can commit contents with // InputConnectionCompat#commitContent() if they match "image/png", "image/gif", // "image/jpeg", or "image/webp". layout.addView(createEditTextWithContentMimeTypes( new String[]{"image/png", "image/gif", "image/jpeg", "image/webp"})); mWebView = (WebView) findViewById(R.id.commit_content_webview); mMimeTypes = (TextView) findViewById(R.id.text_commit_content_mime_types); mLabel = (TextView) findViewById(R.id.text_commit_content_label); mContentUri = (TextView) findViewById(R.id.text_commit_content_content_uri); mLinkUri = (TextView) findViewById(R.id.text_commit_content_link_uri); mFlags = (TextView) findViewById(R.id.text_commit_content_link_flags); if (savedInstanceState != null) { final InputContentInfoCompat previousInputContentInfo = InputContentInfoCompat.wrap( savedInstanceState.getParcelable(INPUT_CONTENT_INFO_KEY)); final int previousFlags = savedInstanceState.getInt(COMMIT_CONTENT_FLAGS_KEY); if (previousInputContentInfo != null) { onCommitContentInternal(previousInputContentInfo, previousFlags); } } } private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] contentMimeTypes) { // Clear the temporary permission (if any). See below about why we do this here. try { if (mCurrentInputContentInfo != null) { mCurrentInputContentInfo.releasePermission(); } } catch (Exception e) { Log.e(TAG, "InputContentInfoCompat#releasePermission() failed.", e); } finally { mCurrentInputContentInfo = null; } mWebView.loadUrl("about:blank"); mMimeTypes.setText(""); mContentUri.setText(""); mLabel.setText(""); mLinkUri.setText(""); mFlags.setText(""); boolean supported = false; for (final String mimeType : contentMimeTypes) { if (inputContentInfo.getDescription().hasMimeType(mimeType)) { supported = true; break; } } if (!supported) { return false; } return onCommitContentInternal(inputContentInfo, flags); } private boolean onCommitContentInternal(InputContentInfoCompat inputContentInfo, int flags) { if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { try { inputContentInfo.requestPermission(); } catch (Exception e) { Log.e(TAG, "InputContentInfoCompat#requestPermission() failed.", e); return false; } } mMimeTypes.setText( Arrays.toString(inputContentInfo.getDescription().filterMimeTypes("*/*"))); mContentUri.setText(inputContentInfo.getContentUri().toString()); mLabel.setText(inputContentInfo.getDescription().getLabel()); Uri linkUri = inputContentInfo.getLinkUri(); mLinkUri.setText(linkUri != null ? linkUri.toString() : "null"); mFlags.setText(flagsToString(flags)); mWebView.loadUrl(inputContentInfo.getContentUri().toString()); mWebView.setBackgroundColor(Color.TRANSPARENT); // Due to the asynchronous nature of WebView, it is a bit too early to call // inputContentInfo.releasePermission() here. Hence we call IC#releasePermission() when this // method is called next time. Note that calling IC#releasePermission() is just to be a // good citizen. Even if we failed to call that method, the system would eventually revoke // the permission sometime after inputContentInfo object gets garbage-collected. mCurrentInputContentInfo = inputContentInfo; mCurrentFlags = flags; return true; } @Override public void onSaveInstanceState(Bundle savedInstanceState) { if (mCurrentInputContentInfo != null) { savedInstanceState.putParcelable(INPUT_CONTENT_INFO_KEY, (Parcelable) mCurrentInputContentInfo.unwrap()); savedInstanceState.putInt(COMMIT_CONTENT_FLAGS_KEY, mCurrentFlags); } mCurrentInputContentInfo = null; mCurrentFlags = 0; super.onSaveInstanceState(savedInstanceState); } /** * Creates a new instance of {@link EditText} that is configured to specify the given content * MIME types to EditorInfo#contentMimeTypes so that developers can locally test how the current * input method behaves for such content MIME types. * * @param contentMimeTypes A {@link String} array that indicates the supported content MIME * types * @return a new instance of {@link EditText}, which specifies EditorInfo#contentMimeTypes with * the given content MIME types */ private EditText createEditTextWithContentMimeTypes(String[] contentMimeTypes) { final CharSequence hintText; final String[] mimeTypes; // our own copy of contentMimeTypes. if (contentMimeTypes == null || contentMimeTypes.length == 0) { hintText = "MIME: []"; mimeTypes = new String[0]; } else { hintText = "MIME: " + Arrays.toString(contentMimeTypes); mimeTypes = Arrays.copyOf(contentMimeTypes, contentMimeTypes.length); } EditText exitText = new EditText(this) { @Override public InputConnection onCreateInputConnection(EditorInfo editorInfo) { final InputConnection ic = super.onCreateInputConnection(editorInfo); EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes); final InputConnectionCompat.OnCommitContentListener callback = new InputConnectionCompat.OnCommitContentListener() { @Override public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { return MainActivity.this.onCommitContent( inputContentInfo, flags, opts, mimeTypes); } }; return InputConnectionCompat.createWrapper(ic, editorInfo, callback); } }; exitText.setHint(hintText); exitText.setTextColor(Color.WHITE); exitText.setHintTextColor(Color.WHITE); return exitText; } /** * Converts {@code flags} specified in {@link InputConnectionCompat#commitContent( * InputConnection, EditorInfo, InputContentInfoCompat, int, Bundle)} to a human readable * string. * * @param flags the 2nd parameter of * {@link InputConnectionCompat#commitContent(InputConnection, EditorInfo, * InputContentInfoCompat, int, Bundle)} * @return a human readable string that corresponds to the given {@code flags} */ private static String flagsToString(int flags) { final ArrayList tokens = new ArrayList<>(); if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { tokens.add("INPUT_CONTENT_GRANT_READ_URI_PERMISSION"); flags &= ~InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION; } if (flags != 0) { tokens.add("0x" + Integer.toHexString(flags)); } return TextUtils.join(" | ", tokens); } }