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(); |
+ } |
+ } |
+} |