| Index: mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java | 
| diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..c455251559f35f3d5cece6872935792b9878cedb | 
| --- /dev/null | 
| +++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java | 
| @@ -0,0 +1,179 @@ | 
| +// Copyright 2014 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +package org.chromium.mojo.shell; | 
| + | 
| +import android.content.Context; | 
| +import android.content.pm.PackageInfo; | 
| +import android.content.pm.PackageManager; | 
| +import android.util.Log; | 
| + | 
| +import java.io.BufferedInputStream; | 
| +import java.io.BufferedOutputStream; | 
| +import java.io.File; | 
| +import java.io.FileInputStream; | 
| +import java.io.FileNotFoundException; | 
| +import java.io.FileOutputStream; | 
| +import java.io.FilenameFilter; | 
| +import java.io.IOException; | 
| +import java.io.InputStream; | 
| +import java.io.OutputStream; | 
| +import java.util.zip.ZipEntry; | 
| +import java.util.zip.ZipInputStream; | 
| + | 
| +/** | 
| + * Helper methods for file extraction from APK assets and zip archives. | 
| + */ | 
| +class FileHelper { | 
| +    public static final String TAG = "MojoFileHelper"; | 
| + | 
| +    // Size of the buffer used in streaming file operations. | 
| +    private static final int BUFFER_SIZE = 1024 * 1024; | 
| +    // Prefix used when naming temporary files. | 
| +    private static final String TEMP_FILE_PREFIX = "temp-"; | 
| +    // Prefix used when naming timestamp files. | 
| +    private static final String TIMESTAMP_PREFIX = "asset_timestamp-"; | 
| + | 
| +    /** | 
| +     * Looks for a timestamp file on disk that indicates the version of the APK that the resource | 
| +     * assets were extracted from. Returns null if a timestamp was found and it indicates that the | 
| +     * resources match the current APK. Otherwise returns a String that represents the filename of a | 
| +     * timestamp to create. | 
| +     */ | 
| +    private static String checkAssetTimestamp(Context context, File outputDir) { | 
| +        PackageManager pm = context.getPackageManager(); | 
| +        PackageInfo pi = null; | 
| + | 
| +        try { | 
| +            pi = pm.getPackageInfo(context.getPackageName(), 0); | 
| +        } catch (PackageManager.NameNotFoundException e) { | 
| +            return TIMESTAMP_PREFIX; | 
| +        } | 
| + | 
| +        if (pi == null) { | 
| +            return TIMESTAMP_PREFIX; | 
| +        } | 
| + | 
| +        String expectedTimestamp = TIMESTAMP_PREFIX + pi.versionCode + "-" + pi.lastUpdateTime; | 
| + | 
| +        String[] timestamps = outputDir.list(new FilenameFilter() { | 
| +            @Override | 
| +            public boolean accept(File dir, String name) { | 
| +                return name.startsWith(TIMESTAMP_PREFIX); | 
| +            } | 
| +        }); | 
| + | 
| +        if (timestamps.length != 1) { | 
| +            // If there's no timestamp, nuke to be safe as we can't tell the age of the files. | 
| +            // If there's multiple timestamps, something's gone wrong so nuke. | 
| +            return expectedTimestamp; | 
| +        } | 
| + | 
| +        if (!expectedTimestamp.equals(timestamps[0])) { | 
| +            return expectedTimestamp; | 
| +        } | 
| + | 
| +        // Timestamp file is already up-to date. | 
| +        return null; | 
| +    } | 
| + | 
| +    public static File extractFromAssets(Context context, String assetName, File outputDirectory, | 
| +            boolean useTempFile) throws IOException, FileNotFoundException { | 
| +        String timestampToCreate = null; | 
| +        if (!useTempFile) { | 
| +            timestampToCreate = checkAssetTimestamp(context, outputDirectory); | 
| +            if (timestampToCreate != null) { | 
| +                for (File child : outputDirectory.listFiles()) { | 
| +                    deleteRecursively(child); | 
| +                } | 
| +            } | 
| +        } | 
| + | 
| +        File outputFile; | 
| +        if (useTempFile) { | 
| +            // Make the original filename part of the temp file name. | 
| +            // TODO(ppi): do we need to sanitize the suffix? | 
| +            String suffix = "-" + assetName; | 
| +            outputFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, outputDirectory); | 
| +        } else { | 
| +            outputFile = new File(outputDirectory, assetName); | 
| +            if (outputFile.exists()) { | 
| +                return outputFile; | 
| +            } | 
| +        } | 
| + | 
| +        BufferedInputStream inputStream = new BufferedInputStream( | 
| +                context.getAssets().open(assetName)); | 
| +        try { | 
| +            writeStreamToFile(inputStream, outputFile); | 
| +        } finally { | 
| +            inputStream.close(); | 
| +        } | 
| + | 
| +        if (timestampToCreate != null) { | 
| +            try { | 
| +                new File(outputDirectory, timestampToCreate).createNewFile(); | 
| +            } catch (IOException e) { | 
| +                // In the worst case we don't write a timestamp, so we'll re-extract the asset next | 
| +                // time. | 
| +                Log.w(TAG, "Failed to write asset timestamp!"); | 
| +            } | 
| +        } | 
| + | 
| +        return outputFile; | 
| +    } | 
| + | 
| +    /** | 
| +     * Extracts the file of the given extension from the archive. Throws FileNotFoundException if no | 
| +     * matching file is found. | 
| +     */ | 
| +    static File extractFromArchive(File archive, String suffixToMatch, | 
| +            File outputDirectory) throws IOException, FileNotFoundException { | 
| +        ZipInputStream zip = new ZipInputStream(new BufferedInputStream(new FileInputStream( | 
| +                archive))); | 
| +        ZipEntry entry; | 
| +        while ((entry = zip.getNextEntry()) != null) { | 
| +            if (entry.getName().endsWith(suffixToMatch)) { | 
| +                // Make the original filename part of the temp file name. | 
| +                // TODO(ppi): do we need to sanitize the suffix? | 
| +                String suffix = "-" + new File(entry.getName()).getName(); | 
| +                File extractedFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, | 
| +                        outputDirectory); | 
| +                writeStreamToFile(zip, extractedFile); | 
| +                zip.close(); | 
| +                return extractedFile; | 
| +            } | 
| +        } | 
| +        zip.close(); | 
| +        throw new FileNotFoundException(); | 
| +    } | 
| + | 
| +    /** | 
| +     * Deletes a file or directory. Directory will be deleted even if not empty. | 
| +     */ | 
| +    static void deleteRecursively(File file) { | 
| +        if (file.isDirectory()) { | 
| +            for (File child : file.listFiles()) { | 
| +                deleteRecursively(child); | 
| +            } | 
| +        } | 
| +        if (!file.delete()) { | 
| +            Log.w(TAG, "Unable to delete file: " + file.getAbsolutePath()); | 
| +        } | 
| +    } | 
| + | 
| +    private static void writeStreamToFile(InputStream inputStream, File outputFile) | 
| +            throws IOException { | 
| +        byte[] buffer = new byte[BUFFER_SIZE]; | 
| +        OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile)); | 
| +        try { | 
| +            int read; | 
| +            while ((read = inputStream.read(buffer, 0, BUFFER_SIZE)) > 0) { | 
| +                outputStream.write(buffer, 0, read); | 
| +            } | 
| +        } finally { | 
| +            outputStream.close(); | 
| +        } | 
| +    } | 
| +} | 
|  |