| Index: chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ae83641d55fa713814cf056836969bc6a8377405
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java
|
| @@ -0,0 +1,294 @@
|
| +// Copyright 2017 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.chrome.browser.photo_picker;
|
| +
|
| +import android.content.ComponentName;
|
| +import android.content.Context;
|
| +import android.content.Intent;
|
| +import android.content.ServiceConnection;
|
| +import android.graphics.Bitmap;
|
| +import android.os.Bundle;
|
| +import android.os.Handler;
|
| +import android.os.IBinder;
|
| +import android.os.Message;
|
| +import android.os.Messenger;
|
| +import android.os.ParcelFileDescriptor;
|
| +import android.os.RemoteException;
|
| +import android.os.StrictMode;
|
| +
|
| +import org.chromium.base.Log;
|
| +
|
| +import java.io.File;
|
| +import java.io.FileDescriptor;
|
| +import java.io.FileInputStream;
|
| +import java.io.IOException;
|
| +import java.lang.ref.WeakReference;
|
| +import java.util.LinkedHashMap;
|
| +
|
| +/**
|
| + * A class to communicate with the {@link DecoderService}.
|
| + */
|
| +public class DecoderServiceHost {
|
| + // A tag for logging error messages.
|
| + private static final String TAG = "ImageDecoderHost";
|
| +
|
| + /**
|
| + * Interface for notifying clients of the service being ready.
|
| + */
|
| + public interface ServiceReadyCallback {
|
| + /**
|
| + * A function to define to receive a notification once the service is up and running.
|
| + */
|
| + void serviceReady();
|
| + }
|
| +
|
| + /**
|
| + * An interface notifying clients when an image has finished decoding.
|
| + */
|
| + public interface ImageDecodedCallback {
|
| + /**
|
| + * A function to define to receive a notification that an image has been decoded.
|
| + * @param filePath The file path for the newly decoded image.
|
| + * @param bitmap The results of the decoding (or placeholder image, if failed).
|
| + */
|
| + void imageDecodedCallback(String filePath, Bitmap bitmap);
|
| + }
|
| +
|
| + /**
|
| + * Class for interacting with the main interface of the service.
|
| + */
|
| + private class DecoderServiceConnection implements ServiceConnection {
|
| + // The callback to use to notify the service being ready.
|
| + private ServiceReadyCallback mCallback;
|
| +
|
| + public DecoderServiceConnection(ServiceReadyCallback callback) {
|
| + mCallback = callback;
|
| + }
|
| +
|
| + // Called when a connection to the service has been established.
|
| + public void onServiceConnected(ComponentName name, IBinder service) {
|
| + mService = new Messenger(service);
|
| + mBound = true;
|
| + mCallback.serviceReady();
|
| + }
|
| +
|
| + // Called when a connection to the service has been lost.
|
| + public void onServiceDisconnected(ComponentName name) {
|
| + mBound = false;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Class for keeping track of the data involved with each request.
|
| + */
|
| + private static class DecoderServiceParams {
|
| + // The path to the file containing the bitmap to decode.
|
| + public String mFilePath;
|
| +
|
| + // The requested size (width and height) of the bitmap, once decoded.
|
| + public int mSize;
|
| +
|
| + // The callback to use to communicate the results of the decoding.
|
| + ImageDecodedCallback mCallback;
|
| +
|
| + public DecoderServiceParams(String filePath, int size, ImageDecodedCallback callback) {
|
| + mFilePath = filePath;
|
| + mSize = size;
|
| + mCallback = callback;
|
| + }
|
| + }
|
| +
|
| + // Map of file paths to decoder parameters in order of request.
|
| + private LinkedHashMap<String, DecoderServiceParams> mRequests = new LinkedHashMap<>();
|
| + LinkedHashMap<String, DecoderServiceParams> getRequests() {
|
| + return mRequests;
|
| + }
|
| +
|
| + // The callback used to notify the client when the service is ready.
|
| + private ServiceReadyCallback mCallback;
|
| +
|
| + // Messenger for communicating with the remote service.
|
| + Messenger mService = null;
|
| +
|
| + // Our service connection to the {@link DecoderService}.
|
| + private DecoderServiceConnection mConnection;
|
| +
|
| + // Flag indicating whether we are bound to the service.
|
| + boolean mBound;
|
| +
|
| + // The inbound messenger used by the remote service to communicate with us.
|
| + final Messenger mMessenger = new Messenger(new IncomingHandler(this));
|
| +
|
| + /**
|
| + * The DecoderServiceHost constructor.
|
| + * @param callback The callback to use when communicating back to the client.
|
| + */
|
| + public DecoderServiceHost(ServiceReadyCallback callback) {
|
| + mCallback = callback;
|
| + }
|
| +
|
| + /**
|
| + * Initiate binding with the {@link DecoderService}.
|
| + * @param context The context to use.
|
| + */
|
| + public void bind(Context context) {
|
| + mConnection = new DecoderServiceConnection(mCallback);
|
| + Intent intent = new Intent(context, DecoderService.class);
|
| + context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
| + }
|
| +
|
| + /**
|
| + * Unbind from the {@link DecoderService}.
|
| + * @param context The context to use.
|
| + */
|
| + public void unbind(Context context) {
|
| + if (mBound) {
|
| + context.unbindService(mConnection);
|
| + mBound = false;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Accepts a request to decode a single image. Queues up the request and reports back
|
| + * asynchronously on |callback|.
|
| + * @param filePath The path to the file to decode.
|
| + * @param size The requested size (width and height) of the resulting bitmap.
|
| + * @param callback The callback to use to communicate the decoding results.
|
| + */
|
| + public void decodeImage(String filePath, int size, ImageDecodedCallback callback) {
|
| + DecoderServiceParams params = new DecoderServiceParams(filePath, size, callback);
|
| + mRequests.put(filePath, params);
|
| + if (mRequests.size() == 1) dispatchNextDecodeImageRequest();
|
| + }
|
| +
|
| + /**
|
| + * Dispatches the next image for decoding (from the queue).
|
| + */
|
| + private void dispatchNextDecodeImageRequest() {
|
| + if (mRequests.entrySet().iterator().hasNext()) {
|
| + DecoderServiceParams params = mRequests.entrySet().iterator().next().getValue();
|
| + dispatchDecodeImageRequest(params.mFilePath, params.mSize);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Ties up all the loose ends from the decoding request (communicates the results of the
|
| + * decoding process back to the client, and takes care of house-keeping chores regarding
|
| + * the request queue).
|
| + * @param filePath The path to the image that was just decoded.
|
| + * @param bitmap The resulting decoded bitmap.
|
| + */
|
| + public void closeRequest(String filePath, Bitmap bitmap) {
|
| + DecoderServiceParams params = getRequests().get(filePath);
|
| + if (params != null) {
|
| + params.mCallback.imageDecodedCallback(filePath, bitmap);
|
| + getRequests().remove(filePath);
|
| + }
|
| + dispatchNextDecodeImageRequest();
|
| + }
|
| +
|
| + /**
|
| + * Communicates with the server to decode a single bitmap.
|
| + * @param filePath The path to the image on disk.
|
| + * @param size The requested width and height of the resulting bitmap.
|
| + */
|
| + private void dispatchDecodeImageRequest(String filePath, int size) {
|
| + // Obtain a file descriptor to send over to the sandboxed process.
|
| + File file = new File(filePath);
|
| + FileInputStream inputFile = null;
|
| + ParcelFileDescriptor pfd = null;
|
| + Bundle bundle = new Bundle();
|
| +
|
| + // The restricted utility process can't open the file to read the
|
| + // contents, so we need to obtain a file descriptor to pass over.
|
| + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
|
| + try {
|
| + try {
|
| + inputFile = new FileInputStream(file);
|
| + FileDescriptor fd = inputFile.getFD();
|
| + pfd = ParcelFileDescriptor.dup(fd);
|
| + bundle.putParcelable(DecoderService.KEY_FILE_DESCRIPTOR, pfd);
|
| + } catch (IOException e) {
|
| + Log.e(TAG, "Unable to obtain FileDescriptor: " + e);
|
| + closeRequest(filePath, null);
|
| + }
|
| + } finally {
|
| + try {
|
| + if (inputFile != null) inputFile.close();
|
| + } catch (IOException e) {
|
| + Log.e(TAG, "Unable to close inputFile: " + e);
|
| + }
|
| + StrictMode.setThreadPolicy(oldPolicy);
|
| + }
|
| +
|
| + if (pfd == null) return;
|
| +
|
| + // Prepare and send the data over.
|
| + Message payload = Message.obtain(null, DecoderService.MSG_DECODE_IMAGE);
|
| + payload.replyTo = mMessenger;
|
| + bundle.putString(DecoderService.KEY_FILE_PATH, filePath);
|
| + bundle.putInt(DecoderService.KEY_SIZE, size);
|
| + payload.setData(bundle);
|
| + try {
|
| + mService.send(payload);
|
| + pfd.close();
|
| + } catch (RemoteException e) {
|
| + Log.e(TAG, "Communications failed (Remote): " + e);
|
| + closeRequest(filePath, null);
|
| + } catch (IOException e) {
|
| + Log.e(TAG, "Communications failed (IO): " + e);
|
| + closeRequest(filePath, null);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Cancels a request to decode an image (if it hasn't already been dispatched).
|
| + * @param filePath The path to the image to cancel decoding.
|
| + */
|
| + public void cancelDecodeImage(String filePath) {
|
| + mRequests.remove(filePath);
|
| + }
|
| +
|
| + /**
|
| + * A class for handling communications from the service to us.
|
| + */
|
| + static class IncomingHandler extends Handler {
|
| + // The DecoderServiceHost object to communicate with.
|
| + private final WeakReference<DecoderServiceHost> mHost;
|
| +
|
| + /**
|
| + * Constructor for IncomingHandler.
|
| + * @param host The DecoderServiceHost object to communicate with.
|
| + */
|
| + IncomingHandler(DecoderServiceHost host) {
|
| + mHost = new WeakReference<DecoderServiceHost>(host);
|
| + }
|
| +
|
| + @Override
|
| + public void handleMessage(Message msg) {
|
| + DecoderServiceHost host = mHost.get();
|
| + if (host == null) {
|
| + super.handleMessage(msg);
|
| + return;
|
| + }
|
| +
|
| + switch (msg.what) {
|
| + case DecoderService.MSG_IMAGE_DECODED_REPLY:
|
| + Bundle payload = msg.getData();
|
| +
|
| + // Read the reply back from the service.
|
| + String filePath = payload.getString(DecoderService.KEY_FILE_PATH);
|
| + Boolean success = payload.getBoolean(DecoderService.KEY_SUCCESS);
|
| + Bitmap bitmap = success
|
| + ? (Bitmap) payload.getParcelable(DecoderService.KEY_IMAGE_BITMAP)
|
| + : null;
|
| + host.closeRequest(filePath, bitmap);
|
| + break;
|
| + default:
|
| + super.handleMessage(msg);
|
| + }
|
| + }
|
| + }
|
| +}
|
|
|