| // 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.IBinder; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.Log; |
| import org.chromium.base.PathUtils; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.annotations.MainDex; |
| import org.chromium.base.library_loader.LibraryLoader; |
| import org.chromium.base.library_loader.LibraryProcessType; |
| import org.chromium.base.library_loader.ProcessInitException; |
| import org.chromium.chrome.browser.init.ChromeBrowserInitializer; |
| |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| |
| /** |
| * A service to accept requests to take image file contents and decode them. |
| */ |
| @MainDex |
| public class DecoderService extends Service { |
| // 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_SIZE = "size"; |
| static final String KEY_SUCCESS = "success"; |
| static final String KEY_DECODE_TIME = "decode_time"; |
| |
| // A tag for logging error messages. |
| private static final String TAG = "ImageDecoder"; |
| |
| // Whether the native library and the sandbox have been initialized. |
| private boolean mNativeLibraryAndSandboxInitialized; |
| |
| @Override |
| public void onCreate() { |
| // DecoderService does not require flags, but LibraryLoader.ensureInitialized() checks for |
| // --enable-low-end-device-mode. Rather than forwarding the flags from the browser process, |
| // just assume no flags. |
| if (!CommandLine.isInitialized()) { |
| CommandLine.init(null); |
| } |
| try { |
| // The decoder service relies on PathUtils. |
| ThreadUtils.runOnUiThreadBlocking(() -> { |
| PathUtils.setPrivateDataDirectorySuffix( |
| ChromeBrowserInitializer.PRIVATE_DATA_DIRECTORY_SUFFIX); |
| }); |
| |
| LibraryLoader.get(LibraryProcessType.PROCESS_CHILD).ensureInitialized(); |
| nativeInitializePhotoPickerSandbox(); |
| |
| mNativeLibraryAndSandboxInitialized = true; |
| } catch (ProcessInitException e) { |
| Log.e(TAG, "Unable to initialize the native library and sandbox", e); |
| } |
| |
| super.onCreate(); |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return mBinder; |
| } |
| |
| private final IDecoderService.Stub mBinder = new IDecoderService.Stub() { |
| @Override |
| public void decodeImage(Bundle payload, IDecoderServiceCallback callback) { |
| Bundle bundle = null; |
| String filePath = ""; |
| int size = 0; |
| try { |
| filePath = payload.getString(KEY_FILE_PATH); |
| ParcelFileDescriptor pfd = payload.getParcelable(KEY_FILE_DESCRIPTOR); |
| size = payload.getInt(KEY_SIZE); |
| |
| // Setup a minimum viable response to parent process. Will be fleshed out |
| // further below. |
| bundle = new Bundle(); |
| bundle.putString(KEY_FILE_PATH, filePath); |
| bundle.putBoolean(KEY_SUCCESS, false); |
| |
| if (!mNativeLibraryAndSandboxInitialized) { |
| Log.e(TAG, "Decode failed %s (size: %d): no sandbox", filePath, size); |
| sendReply(callback, bundle); // Sends SUCCESS == false; |
| return; |
| } |
| |
| FileDescriptor fd = pfd.getFileDescriptor(); |
| |
| long begin = SystemClock.elapsedRealtime(); |
| Bitmap bitmap = BitmapUtils.decodeBitmapFromFileDescriptor(fd, size); |
| long decodeTime = SystemClock.elapsedRealtime() - begin; |
| |
| try { |
| pfd.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "Closing failed " + filePath + " (size: " + size + ") " + e); |
| } |
| |
| if (bitmap == null) { |
| Log.e(TAG, "Decode failed " + filePath + " (size: " + size + ")"); |
| sendReply(callback, bundle); // Sends SUCCESS == false; |
| return; |
| } |
| |
| // The most widely supported, easiest, and reasonably efficient method is to |
| // decode to an immutable bitmap and just return the bitmap over binder. It |
| // will internally memcpy itself to ashmem and then just send over the file |
| // descriptor. In the receiving process it will just leave the bitmap on |
| // ashmem since it's immutable and carry on. |
| bundle.putParcelable(KEY_IMAGE_BITMAP, bitmap); |
| bundle.putBoolean(KEY_SUCCESS, true); |
| bundle.putLong(KEY_DECODE_TIME, decodeTime); |
| sendReply(callback, bundle); |
| bitmap.recycle(); |
| } catch (Exception e) { |
| // 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. |
| Log.e(TAG, |
| "Unexpected error during decoding " + filePath + " (size: " + size + ") " |
| + e); |
| |
| if (bundle != null) sendReply(callback, bundle); |
| } |
| } |
| |
| private void sendReply(IDecoderServiceCallback callback, Bundle bundle) { |
| try { |
| callback.onDecodeImageDone(bundle); |
| } catch (RemoteException remoteException) { |
| Log.e(TAG, "Remote error while replying: " + remoteException); |
| } |
| } |
| }; |
| |
| // Initializes the seccomp-bpf sandbox when it's supported by the device. Records the sandbox |
| // status to the Android.SeccompStatus.PhotoPickerSandbox histogram. |
| private static native void nativeInitializePhotoPickerSandbox(); |
| } |