Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1925)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java

Issue 2816733002: Photo Picker Dialog: Use sandboxed utility process for decoding images. (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..26cc28c631d65061fe66102918824ac4c0c1e085
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java
@@ -0,0 +1,356 @@
+// 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.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+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 java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.util.LinkedHashMap;
+
+/**
+ * A class to communicate with the decoder service.
+ */
+public class DecoderServiceHost {
+ /**
+ * 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);
+
+ try {
+ Message msg = Message.obtain(null, DecoderService.MSG_REGISTER_CLIENT);
+ msg.replyTo = mMessenger;
+ mService.send(msg);
+ } catch (RemoteException e) {
+ return;
+ }
+
+ 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 class DecoderServiceParams {
+ // The path to the file containing the bitmap to decode.
+ public String mFilePath;
+
+ // The requested width of the bitmap, once decoded.
+ public int mWidth;
+
+ // The callback to use to communicate the results of the decoding.
+ ImageDecodedCallback mCallback;
+
+ public DecoderServiceParams(String filePath, int width, ImageDecodedCallback callback) {
+ mFilePath = filePath;
+ mWidth = width;
+ 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 the client wants us to use to report back when the service is ready.
Theresa 2017/04/13 02:25:27 optional style nit: This could be re-written more
Finnur 2017/04/18 17:21:15 Done.
+ private ServiceReadyCallback mCallback;
+
+ // Messenger for communicating with the remote service.
+ Messenger mService = null;
+
+ // Our service connection to the decoder service.
+ 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 decoder service.
+ * @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 decoder service.
+ * @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 width The requested width of the resulting bitmap.
+ * @param callback The callback to use to communicate the decoding results.
+ */
+ public void decodeImage(String filePath, int width, ImageDecodedCallback callback) {
+ DecoderServiceParams params = new DecoderServiceParams(filePath, width, callback);
+ mRequests.put(filePath, params);
+ if (mRequests.size() == 1) {
+ dispatchNextDecodeImageRequest();
+ }
+ }
+
+ /**
+ * Dispatches the next image for decoding (from the queue).
+ */
+ private void dispatchNextDecodeImageRequest() {
+ for (DecoderServiceParams params : mRequests.values()) {
Theresa 2017/04/13 02:25:27 Why does decodeImage() need to check if mRequests.
Finnur 2017/04/18 17:21:15 It doesn't loop through all (there's a break state
+ dispatchDecodeImageRequest(params.mFilePath, params.mWidth);
+ break;
+ }
+ }
+
+ /**
+ * Communicates with the server to decode a single bitmap.
+ * @param filePath The path to the image on disk.
+ * @param width The requested width of the resulting bitmap.
+ */
+ private void dispatchDecodeImageRequest(String filePath, int width) {
+ // 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
Theresa 2017/04/13 02:25:27 Is there Android documentation somewhere explainin
Finnur 2017/04/18 17:21:15 android:isolatedProcess If set to true, this servi
+ // 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) {
+ e.printStackTrace();
+ }
+ } finally {
+ try {
+ inputFile.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+
+ if (pfd == null) {
+ return;
+ }
+
+ // Prepare and send the data over.
+ Message payload = Message.obtain(null, DecoderService.MSG_DECODE_IMAGE);
+ bundle.putString(DecoderService.KEY_FILE_PATH, filePath);
+ bundle.putInt(DecoderService.KEY_WIDTH, width);
+ payload.setData(bundle);
+ try {
+ mService.send(payload);
+ pfd.close();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Cancels a request to decode an image (if it hasn't already been dispatched).
+ * @param filePath
Theresa 2017/04/13 02:25:27 complete this JavaDoc
Finnur 2017/04/18 17:21:15 Done.
+ */
+ public void cancelDecodeImage(String filePath) {
+ mRequests.remove(filePath);
+ }
+
+ /**
+ * A class for handling communications from the server 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 = payload.getParcelable(DecoderService.KEY_IMAGE_BITMAP);
+ int width = payload.getInt(DecoderService.KEY_WIDTH);
+ int height = width;
+
+ if (!success) {
+ closeRequest(host, filePath, createPlaceholderBitmap(width, height));
+ return;
+ }
+
+ // Direct passing of bitmaps via ashmem became available in Marshmallow. For
+ // older clients, we manage our own memory file.
+ if (bitmap == null) {
+ ParcelFileDescriptor pfd =
+ payload.getParcelable(DecoderService.KEY_IMAGE_DESCRIPTOR);
+ int byteCount = payload.getInt(DecoderService.KEY_IMAGE_BYTE_COUNT);
+
+ // Grab the decoded pixels from memory and construct a bitmap object.
+ FileInputStream inFile = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ byte[] pixels = new byte[byteCount];
+
+ try {
+ try {
+ inFile.read(pixels, 0, byteCount);
+ ByteBuffer buffer = ByteBuffer.wrap(pixels);
+ bitmap =
+ Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ bitmap.copyPixelsFromBuffer(buffer);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } finally {
+ try {
+ inFile.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // Reply back to the original caller.
+ closeRequest(host, filePath, bitmap);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ /**
+ * Creates a placeholder bitmap, used when the server failed to decode the image.
+ * @param width The requested width of the resulting bitmap.
+ * @param height The requested height of the resulting bitmap.
+ * @return Placeholder bitmap.
+ */
+ private Bitmap createPlaceholderBitmap(int width, int height) {
Theresa 2017/04/13 02:25:27 Instead of creating a placeholder bitmap, can we s
Finnur 2017/04/18 17:21:15 Yeah, that probably works. I'll take a look at tha
+ Bitmap placeholder = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(placeholder);
+ Paint paint = new Paint();
+ paint.setColor(Color.GRAY);
+ canvas.drawRect(0, 0, (float) width, (float) height, paint);
+ return placeholder;
+ }
+
+ /**
+ * Ties up all the lose 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 host The DecoderServiceHost object to communicate with.
+ * @param filePath The path to the image that was just decoded.
+ * @param bitmap The resulting decoded bitmap.
+ */
+ private void closeRequest(DecoderServiceHost host, String filePath, Bitmap bitmap) {
+ DecoderServiceParams params = host.getRequests().get(filePath);
+ if (params != null) {
+ params.mCallback.imageDecodedCallback(filePath, bitmap);
+ host.getRequests().remove(filePath);
+ }
+ host.dispatchNextDecodeImageRequest();
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698