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

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderService.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/DecoderService.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java
new file mode 100644
index 0000000000000000000000000000000000000000..acc2ef03901dc8628a6513f2d446c79c67da6ab2
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java
@@ -0,0 +1,237 @@
+// 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.app.Service;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.MemoryFile;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+/**
+ * A service to accept requests to take image file contents and decode them.
+ */
+public class DecoderService extends Service {
+ // Message ids for communicating with the client.
+
+ // A message sent by the client to register as a consumer of this service.
+ static final int MSG_REGISTER_CLIENT = 1;
+ // A message sent by the client to decode an image.
+ static final int MSG_DECODE_IMAGE = 2;
+ // A message sent by the server to notify the client of the results of the decoding.
+ static final int MSG_IMAGE_DECODED_REPLY = 3;
+
+ // The keys for the bundle when passing data to and from this service.
+ static final String KEY_FILE_DESCRIPTOR = "file_descriptor";
+ static final String KEY_FILE_PATH = "file_path";
+ static final String KEY_IMAGE_BITMAP = "image_bitmap";
+ static final String KEY_IMAGE_BYTE_COUNT = "image_byte_count";
+ static final String KEY_IMAGE_DESCRIPTOR = "image_descriptor";
+ static final String KEY_WIDTH = "width";
Theresa 2017/04/13 02:25:27 Since this represents width and height, could it b
Finnur 2017/04/18 17:21:14 Done.
+ static final String KEY_SUCCESS = "success";
+
+ // A method for getFileDescriptor, obtained via Reflection. Can be null if not supported by
Theresa 2017/04/13 02:25:27 We shouldn't be accessing methods that aren't publ
Finnur 2017/04/18 17:21:14 If by 'this' you mean 'obtain a FileDescriptor for
Theresa 2017/04/18 19:06:20 By "this" I mean a solution that doesn't use refle
Finnur 2017/04/19 16:01:35 I would appreciate it if you could recommend some
Theresa 2017/04/19 17:31:14 This is my understanding of the current process, a
+ // the Android OS.
+ private static final Method sMethodGetFileDescriptor;
+ static {
+ sMethodGetFileDescriptor = getMethod("getFileDescriptor");
+ }
+
+ // A method for createAshmemBitmap, obtained via Reflection. Can be null if not supported by
+ // the Android OS.
+ private static final Method sMethodCreateAshmemBitmap;
+ static {
+ sMethodCreateAshmemBitmap = getMethod("createAshmemBitmap");
+ }
+
+ /**
+ * A helper function to look up methods by name.
+ * @param name The name of the method to look up.
+ * @return The method, if found. Otherwise null.
+ */
+ private static Method getMethod(String name) {
+ try {
+ if (name.equals("getFileDescriptor")) {
+ return MemoryFile.class.getDeclaredMethod(name);
+ }
+ if (name.equals("createAshmemBitmap")) {
+ return Bitmap.class.getDeclaredMethod(name);
+ }
+ return null;
+ } catch (NoSuchMethodException e) {
+ if (name.equals("createAshmemBitmap")) {
+ return null; // Expected error on pre-M devices.
+ }
+
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * A helper function to obtain a FileDescriptor from a MemoryFile.
+ * @param file The MemoryFile to get a FileDescriptor for.
+ * @return The resulting FileDescriptor.
+ */
+ public static FileDescriptor getFileDescriptor(MemoryFile file) {
+ try {
+ return (FileDescriptor) sMethodGetFileDescriptor.invoke(file);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * A helper function to obtain an ashmemBitmap version of a regular |bitmap|.
+ * @param bitmap The bitmap to use.
+ * @return an ashmemBitmap.
+ */
+ public static Bitmap createAshmemBitmap(Bitmap bitmap) {
+ try {
+ return (Bitmap) sMethodCreateAshmemBitmap.invoke(bitmap);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Handler for incoming messages from clients.
+ */
+ static class IncomingHandler extends Handler {
+ // The client we are communicating with.
+ private Messenger mClient;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REGISTER_CLIENT:
Yusuf 2017/04/13 19:05:25 why do we need the two separate messages here? ie,
Finnur 2017/04/21 14:47:55 Yeah, that's much better. Done.
+ mClient = msg.replyTo;
+ break;
+ case MSG_DECODE_IMAGE:
+ Bundle bundle = new Bundle();
+ try {
+ Bundle payload = msg.getData();
+
+ String filePath = payload.getString(KEY_FILE_PATH);
+ ParcelFileDescriptor pfd = payload.getParcelable(KEY_FILE_DESCRIPTOR);
+ int width = payload.getInt(KEY_WIDTH);
+
+ // Setup a minimum viable response to parent process. Will be fleshed out
+ // further below.
+ bundle.putString(KEY_FILE_PATH, filePath);
+ bundle.putInt(KEY_WIDTH, width);
+ bundle.putBoolean(KEY_SUCCESS, false);
+
+ FileDescriptor fd = pfd.getFileDescriptor();
+ Bitmap bitmap = BitmapUtils.decodeBitmapFromFileDescriptor(fd, width);
+ try {
+ pfd.close();
+ } catch (IOException e) {
+ e.printStackTrace();
Yusuf 2017/04/13 19:05:25 Log an error message with details if applicable, r
Finnur 2017/04/21 14:47:55 Done.
+ }
+
+ MemoryFile imageFile = null;
+ ParcelFileDescriptor imagePfd = null;
+
+ // createAshmemBitmap was not available until Marshmallow.
+ if (sMethodCreateAshmemBitmap != null) {
+ Bitmap ashmemBitmap = createAshmemBitmap(bitmap);
+ bitmap.recycle();
+ bundle.putParcelable(KEY_IMAGE_BITMAP, ashmemBitmap);
+ bundle.putBoolean(KEY_SUCCESS, true);
+ } else {
+ int byteCount = bitmap.getByteCount();
+
+ ByteBuffer buffer = ByteBuffer.allocate(byteCount);
+ bitmap.copyPixelsToBuffer(buffer);
+ bitmap.recycle();
+ buffer.rewind();
+
+ try {
+ imageFile = new MemoryFile(filePath, byteCount);
+ imageFile.writeBytes(buffer.array(), 0, 0, byteCount);
+
+ fd = getFileDescriptor(imageFile);
+ imagePfd = ParcelFileDescriptor.dup(fd);
+ } catch (IOException e) {
+ e.printStackTrace();
Yusuf 2017/04/13 19:05:24 see above.
Finnur 2017/04/21 14:47:55 Done.
+ }
+
+ bundle.putParcelable(KEY_IMAGE_DESCRIPTOR, imagePfd);
+ bundle.putInt(KEY_IMAGE_BYTE_COUNT, byteCount);
+ bundle.putBoolean(KEY_SUCCESS, true);
Theresa 2017/04/13 02:25:27 If there's an IOException, should we still be retu
Finnur 2017/04/18 17:21:14 Hmm... no, we should probably not do so...
+ }
+
+ try {
+ Message reply = Message.obtain(null, MSG_IMAGE_DECODED_REPLY);
+ reply.setData(bundle);
+ mClient.send(reply);
+ bundle = null;
+ } catch (RemoteException e) {
Yusuf 2017/04/13 19:05:25 RemoteException can be a DeadObject or a Transacti
Finnur 2017/04/21 14:47:55 So... at the moment, we don't send _any_ bitmap da
+ mClient = null; // He's dead, Jim.
+ }
+
+ if (imageFile != null) {
Yusuf 2017/04/13 19:05:25 one line
Finnur 2017/04/21 14:47:55 Done.
+ imageFile.close();
+ }
+ try {
+ if (imagePfd != null) {
Yusuf 2017/04/13 19:05:25 one line
Finnur 2017/04/21 14:47:55 Done.
+ imagePfd.close();
+ }
+ } catch (IOException e) {
Yusuf 2017/04/13 19:05:24 see above
Finnur 2017/04/21 14:47:55 Done.
+ e.printStackTrace();
+ }
+ } catch (Exception e) {
Yusuf 2017/04/13 19:05:25 but why would it crash? (You have all these other
Finnur 2017/04/21 14:47:55 StrictMode? I don't know... :s My thinking was th
+ // This service has no UI and maintains no state so if it crashes on
+ // decoding a photo, it is better UX to eat the exception instead of showing
+ // a crash dialog and discarding other requests that have already been sent.
+ e.printStackTrace();
+
+ if (bundle != null && mClient != null) {
+ Message reply = Message.obtain(null, MSG_IMAGE_DECODED_REPLY);
Yusuf 2017/04/13 19:05:25 add a private send message below and avoid duplica
Finnur 2017/04/21 14:47:55 Done.
+ reply.setData(bundle);
+ try {
+ mClient.send(reply);
+ } catch (RemoteException remoteException) {
Yusuf 2017/04/13 19:05:25 see above
Finnur 2017/04/21 14:47:55 Done.
+ mClient = null; // He's dead, Jim.
+ }
+ }
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ /**
+ * The target we publish for clients to send messages to IncomingHandler.
+ */
+ final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+ /**
+ * When binding to the service, we return an interface to our messenger
+ * for sending messages to the service.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698