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