blob: 40d4fef134da727a8ed0c7aae9455f289053fe80 [file] [log] [blame]
// Copyright 2015 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.services.camera_roll;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.webkit.MimeTypeMap;
import org.chromium.mojo.application.ApplicationConnection;
import org.chromium.mojo.application.ApplicationDelegate;
import org.chromium.mojo.application.ApplicationRunner;
import org.chromium.mojo.application.ServiceFactoryBinder;
import org.chromium.mojo.bindings.InterfaceRequest;
import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.DataPipe;
import org.chromium.mojo.system.MessagePipeHandle;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojo.system.MojoResult;
import org.chromium.mojo.system.Pair;
import org.chromium.mojom.mojo.CameraRollService;
import org.chromium.mojom.mojo.Photo;
import org.chromium.mojom.mojo.Shell;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Implementation of org.chromium.mojom.mojo.CameraRollService.
*/
class CameraRollServiceImpl implements CameraRollService {
private static final String TAG = "CameraRollServiceImpl";
private final Core mCore;
private final Executor mExecutor;
private ArrayList<File> mPhotos;
class CopyToPipeJob implements Runnable {
@SuppressWarnings("hiding")
private static final String TAG = "CopyToPipeJob";
private final File mPhotoFile;
private DataPipe.ProducerHandle mProducer;
CopyToPipeJob(File photoFile, DataPipe.ProducerHandle producerHandle) {
mPhotoFile = photoFile;
mProducer = producerHandle;
}
@Override
public void run() {
try (FileInputStream fileInputStream = new FileInputStream(mPhotoFile);
FileChannel channel = fileInputStream.getChannel()) {
int bytesRead = 0;
do {
try {
ByteBuffer buffer = mProducer.beginWriteData(0, DataPipe.WriteFlags.none());
bytesRead = channel.read(buffer);
mProducer.endWriteData(bytesRead == -1 ? 0 : bytesRead);
} catch (MojoException e) {
if (e.getMojoResult() == MojoResult.SHOULD_WAIT) {
mCore.wait(
mProducer, Core.HandleSignals.WRITABLE, Core.DEADLINE_INFINITE);
} else if (e.getMojoResult() == MojoResult.FAILED_PRECONDITION) {
break;
} else {
throw e;
}
}
} while (bytesRead != -1);
} catch (IOException e) {
Log.e(TAG, e.toString());
} finally {
mProducer.close();
}
}
}
CameraRollServiceImpl(Core core, Executor executor) {
mCore = core;
mExecutor = executor;
constructPhotoGallery();
}
/**
* Updates the list from which |getPhoto| returns its result.
*
* @see org.chromium.mojom.mojo.CameraRollService#update()
*/
@Override
public void update() {
constructPhotoGallery();
}
/**
* Returns the number of photos available in the camera roll.
*
* @see org.chromium.mojom.mojo.CameraRollService#getCount(GetCountResponse)
*/
@Override
public void getCount(CameraRollService.GetCountResponse callback) {
callback.call(mPhotos.size());
}
/**
* Returns the photo from the current snapshot of the camera roll at the requested index.
*
* @see org.chromium.mojom.mojo.CameraRollService#getPhoto(int, GetPhotoResponse)
*/
@Override
public void getPhoto(int index, CameraRollService.GetPhotoResponse callback) {
if (index >= mPhotos.size()) {
callback.call(null);
return;
}
File photoFile = mPhotos.get(index);
Photo photo = new Photo();
try {
String uniqueId = Long.toString(photoFile.lastModified()) + photoFile.getPath();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(uniqueId.getBytes("UTF-8"));
byte[] hash = md.digest();
BigInteger bigInt = new BigInteger(1, hash);
photo.uniqueId = bigInt.toString(16);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, e.toString());
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.toString());
}
Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = mCore.createDataPipe(null);
photo.content = handles.second;
callback.call(photo);
mExecutor.execute(new CopyToPipeJob(photoFile, handles.first));
}
private void constructPhotoGallery() {
mPhotos = new ArrayList<File>();
File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
findAllImageFiles(dcimDir, mPhotos);
Collections.sort(mPhotos, Collections.reverseOrder(new Comparator<File>() {
@Override
public int compare(File f1, File f2) {
return Long.compare(f1.lastModified(), f2.lastModified());
}
}));
}
private void findAllImageFiles(File rootDir, ArrayList<File> outputImageFiles) {
for (File file : rootDir.listFiles()) {
if (file.isHidden()) {
continue;
}
if (file.isDirectory()) {
findAllImageFiles(file, outputImageFiles);
}
if (file.isFile()) {
String extension = MimeTypeMap.getFileExtensionFromUrl(file.toURI().toString());
if (extension != null) {
String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (type != null && type.contains("image")) {
outputImageFiles.add(file);
}
}
}
}
}
/**
* When connection is closed, we stop receiving location updates.
*
* @see org.chromium.mojo.bindings.Interface#close()
*/
@Override
public void close() {
}
/**
* @see org.chromium.mojo.bindings.Interface#onConnectionError(MojoException)
*/
@Override
public void onConnectionError(MojoException e) {
}
}
/**
* Android application for running the |CameraRollService|.
*/
public class CameraRollApp implements ApplicationDelegate {
private final Core mCore;
private final ExecutorService mThreadPool;
CameraRollApp(Core core) {
mCore = core;
mThreadPool = Executors.newCachedThreadPool();
}
/**
* @see ApplicationDelegate#initialize(Shell, String[], String)
*/
@Override
public void initialize(Shell shell, String[] args, String url) {
}
/**
* @see ApplicationDelegate#configureIncomingConnection(ApplicationConnection)
*/
@Override
public boolean configureIncomingConnection(ApplicationConnection connection) {
connection.addService(new ServiceFactoryBinder<CameraRollService>() {
@Override
public void bind(InterfaceRequest<CameraRollService> request) {
CameraRollService.MANAGER.bind(
new CameraRollServiceImpl(mCore, mThreadPool), request);
}
@Override
public String getInterfaceName() {
return CameraRollService.MANAGER.getName();
}
});
return true;
}
/**
* @see ApplicationDelegate#quit()
*/
@Override
public void quit() {
mThreadPool.shutdown();
}
public static void mojoMain(
@SuppressWarnings("unused") Context context, Core core,
MessagePipeHandle applicationRequestHandle) {
ApplicationRunner.run(new CameraRollApp(core), core, applicationRequestHandle);
}
}