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