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