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