close
The Wayback Machine - https://web.archive.org/web/20161125171326/https://developer.android.com/samples/CommitContentSampleIME/src/com.example.android.commitcontent.ime/ImageKeyboard.html
Skip to content

Most visited

Recently visited

navigation
CommitContentSampleIME / src / com.example.android.commitcontent.ime /

ImageKeyboard.java

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.ime;
18
 
19
import android.app.AppOpsManager;
20
import android.content.ClipDescription;
21
import android.content.Context;
22
import android.content.Intent;
23
import android.content.pm.PackageManager;
24
import android.inputmethodservice.InputMethodService;
25
import android.net.Uri;
26
import android.os.Build;
27
import android.support.annotation.NonNull;
28
import android.support.annotation.Nullable;
29
import android.support.annotation.RawRes;
30
import android.support.v13.view.inputmethod.EditorInfoCompat;
31
import android.support.v13.view.inputmethod.InputConnectionCompat;
32
import android.support.v13.view.inputmethod.InputContentInfoCompat;
33
import android.support.v4.content.FileProvider;
34
import android.util.Log;
35
import android.view.View;
36
import android.view.inputmethod.EditorInfo;
37
import android.view.inputmethod.InputBinding;
38
import android.view.inputmethod.InputConnection;
39
import android.widget.Button;
40
import android.widget.LinearLayout;
41
 
42
import java.io.File;
43
import java.io.FileOutputStream;
44
import java.io.IOException;
45
import java.io.InputStream;
46
import java.io.OutputStream;
47
 
48
 
49
public class ImageKeyboard extends InputMethodService {
50
 
51
    private static final String TAG = "ImageKeyboard";
52
    private static final String AUTHORITY = "com.example.android.supportv13.sampleime.inputcontent";
53
    private static final String MIME_TYPE_GIF = "image/gif";
54
    private static final String MIME_TYPE_PNG = "image/png";
55
    private static final String MIME_TYPE_WEBP = "image/webp";
56
 
57
    private File mPngFile;
58
    private File mGifFile;
59
    private File mWebpFile;
60
    private Button mGifButton;
61
    private Button mPngButton;
62
    private Button mWebpButton;
63
 
64
    private boolean isCommitContentSupported(
65
            @Nullable EditorInfo editorInfo, @NonNull String mimeType) {
66
        if (editorInfo == null) {
67
            return false;
68
        }
69
 
70
        final InputConnection ic = getCurrentInputConnection();
71
        if (ic == null) {
72
            return false;
73
        }
74
 
75
        if (!validatePackageName(editorInfo)) {
76
            return false;
77
        }
78
 
79
        final String[] supportedMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);
80
        for (String supportedMimeType : supportedMimeTypes) {
81
            if (ClipDescription.compareMimeTypes(mimeType, supportedMimeType)) {
82
                return true;
83
            }
84
        }
85
        return false;
86
    }
87
 
88
    private void doCommitContent(@NonNull String description, @NonNull String mimeType,
89
            @NonNull File file) {
90
        final EditorInfo editorInfo = getCurrentInputEditorInfo();
91
 
92
        // Validate packageName again just in case.
93
        if (!validatePackageName(editorInfo)) {
94
            return;
95
        }
96
 
97
        final Uri contentUri = FileProvider.getUriForFile(this, AUTHORITY, file);
98
 
99
        // As you as an IME author are most likely to have to implement your own content provider
100
        // to support CommitContent API, it is important to have a clear spec about what
101
        // applications are going to be allowed to access the content that your are going to share.
102
        final int flag;
103
        if (Build.VERSION.SDK_INT >= 25) {
104
            // On API 25 and later devices, as an analogy of Intent.FLAG_GRANT_READ_URI_PERMISSION,
105
            // you can specify InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION to give
106
            // a temporary read access to the recipient application without exporting your content
107
            // provider.
108
            flag = InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
109
        } else {
110
            // On API 24 and prior devices, we cannot rely on
111
            // InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION. You as an IME author
112
            // need to decide what access control is needed (or not needed) for content URIs that
113
            // you are going to expose. This sample uses Context.grantUriPermission(), but you can
114
            // implement your own mechanism that satisfies your own requirements.
115
            flag = 0;
116
            try {
117
                // TODO: Use revokeUriPermission to revoke as needed.
118
                grantUriPermission(
119
                        editorInfo.packageName, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
120
            } catch (Exception e){
121
                Log.e(TAG, "grantUriPermission failed packageName=" + editorInfo.packageName
122
                        + " contentUri=" + contentUri, e);
123
            }
124
        }
125
 
126
        final InputContentInfoCompat inputContentInfoCompat = new InputContentInfoCompat(
127
                contentUri,
128
                new ClipDescription(description, new String[]{mimeType}),
129
                null /* linkUrl */);
130
        InputConnectionCompat.commitContent(
131
                getCurrentInputConnection(), getCurrentInputEditorInfo(), inputContentInfoCompat,
132
                flag, null);
133
    }
134
 
135
    private boolean validatePackageName(@Nullable EditorInfo editorInfo) {
136
        if (editorInfo == null) {
137
            return false;
138
        }
139
        final String packageName = editorInfo.packageName;
140
        if (packageName == null) {
141
            return false;
142
        }
143
 
144
        // In Android L MR-1 and prior devices, EditorInfo.packageName is not a reliable identifier
145
        // of the target application because:
146
        //   1. the system does not verify it [1]
147
        //   2. InputMethodManager.startInputInner() had filled EditorInfo.packageName with
148
        //      view.getContext().getPackageName() [2]
149
        // [1]: https://android.googlesource.com/platform/frameworks/base/+/a0f3ad1b5aabe04d9eb1df8bad34124b826ab641
150
        // [2]: https://android.googlesource.com/platform/frameworks/base/+/02df328f0cd12f2af87ca96ecf5819c8a3470dc8
151
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
152
            return true;
153
        }
154
 
155
        final InputBinding inputBinding = getCurrentInputBinding();
156
        if (inputBinding == null) {
157
            // Due to b.android.com/225029, it is possible that getCurrentInputBinding() returns
158
            // null even after onStartInputView() is called.
159
            // TODO: Come up with a way to work around this bug....
160
            Log.e(TAG, "inputBinding should not be null here. "
161
                    + "You are likely to be hitting b.android.com/225029");
162
            return false;
163
        }
164
        final int packageUid = inputBinding.getUid();
165
 
166
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
167
            final AppOpsManager appOpsManager =
168
                    (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
169
            try {
170
                appOpsManager.checkPackage(packageUid, packageName);
171
            } catch (Exception e) {
172
                return false;
173
            }
174
            return true;
175
        }
176
 
177
        final PackageManager packageManager = getPackageManager();
178
        final String possiblePackageNames[] = packageManager.getPackagesForUid(packageUid);
179
        for (final String possiblePackageName : possiblePackageNames) {
180
            if (packageName.equals(possiblePackageName)) {
181
                return true;
182
            }
183
        }
184
        return false;
185
    }
186
 
187
    @Override
188
    public void onCreate() {
189
        super.onCreate();
190
 
191
        // TODO: Avoid file I/O in the main thread.
192
        final File imagesDir = new File(getFilesDir(), "images");
193
        imagesDir.mkdirs();
194
        mGifFile = getFileForResource(this, R.raw.animated_gif, imagesDir, "image.gif");
195
        mPngFile = getFileForResource(this, R.raw.dessert_android, imagesDir, "image.png");
196
        mWebpFile = getFileForResource(this, R.raw.animated_webp, imagesDir, "image.webp");
197
    }
198
 
199
    @Override
200
    public View onCreateInputView() {
201
        mGifButton = new Button(this);
202
        mGifButton.setText("Insert GIF");
203
        mGifButton.setOnClickListener(new View.OnClickListener() {
204
            @Override
205
            public void onClick(View view) {
206
                ImageKeyboard.this.doCommitContent("A waving flag", MIME_TYPE_GIF, mGifFile);
207
            }
208
        });
209
 
210
        mPngButton = new Button(this);
211
        mPngButton.setText("Insert PNG");
212
        mPngButton.setOnClickListener(new View.OnClickListener() {
213
            @Override
214
            public void onClick(View view) {
215
                ImageKeyboard.this.doCommitContent("A droid logo", MIME_TYPE_PNG, mPngFile);
216
            }
217
        });
218
 
219
        mWebpButton = new Button(this);
220
        mWebpButton.setText("Insert WebP");
221
        mWebpButton.setOnClickListener(new View.OnClickListener() {
222
            @Override
223
            public void onClick(View view) {
224
                ImageKeyboard.this.doCommitContent(
225
                        "Android N recovery animation", MIME_TYPE_WEBP, mWebpFile);
226
            }
227
        });
228
 
229
        final LinearLayout layout = new LinearLayout(this);
230
        layout.setOrientation(LinearLayout.VERTICAL);
231
        layout.addView(mGifButton);
232
        layout.addView(mPngButton);
233
        layout.addView(mWebpButton);
234
        return layout;
235
    }
236
 
237
    @Override
238
    public boolean onEvaluateFullscreenMode() {
239
        // In full-screen mode the inserted content is likely to be hidden by the IME. Hence in this
240
        // sample we simply disable full-screen mode.
241
        return false;
242
    }
243
 
244
    @Override
245
    public void onStartInputView(EditorInfo info, boolean restarting) {
246
        mGifButton.setEnabled(mGifFile != null && isCommitContentSupported(info, MIME_TYPE_GIF));
247
        mPngButton.setEnabled(mPngFile != null && isCommitContentSupported(info, MIME_TYPE_PNG));
248
        mWebpButton.setEnabled(mWebpFile != null && isCommitContentSupported(info, MIME_TYPE_WEBP));
249
    }
250
 
251
    private static File getFileForResource(
252
            @NonNull Context context, @RawRes int res, @NonNull File outputDir,
253
            @NonNull String filename) {
254
        final File outputFile = new File(outputDir, filename);
255
        final byte[] buffer = new byte[4096];
256
        InputStream resourceReader = null;
257
        try {
258
            try {
259
                resourceReader = context.getResources().openRawResource(res);
260
                OutputStream dataWriter = null;
261
                try {
262
                    dataWriter = new FileOutputStream(outputFile);
263
                    while (true) {
264
                        final int numRead = resourceReader.read(buffer);
265
                        if (numRead <= 0) {
266
                            break;
267
                        }
268
                        dataWriter.write(buffer, 0, numRead);
269
                    }
270
                    return outputFile;
271
                } finally {
272
                    if (dataWriter != null) {
273
                        dataWriter.flush();
274
                        dataWriter.close();
275
                    }
276
                }
277
            } finally {
278
                if (resourceReader != null) {
279
                    resourceReader.close();
280
                }
281
            }
282
        } catch (IOException e) {
283
            return null;
284
        }
285
    }
286
}
This site uses cookies to store your preferences for site-specific language and display options.

Hooray!

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a one-minute survey?
Help us improve Android tools and documentation.