/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.cellbots.remoteEyes;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.http.client.ClientProtocolException;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.SurfaceHolder.Callback;
import android.view.View.OnClickListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* Remote eyes turns your Android device into a wireless web cam. With Froyo's
* camera enhancements (ability to manage the preview memory buffer + access to
* Skia for quickly converting from YUV to JPG), it is possible to capture and
* upload preview frames fast enough for it to look like video. The behavior is
* that a new frame will not be captured and processed until the previous frame
* has been processed and uploaded. This means that if the connection slows
* down, the video may have brief stutters, but it won't fall behind. See
* put.php for a simple, working example of what is running server side. Once
* you have put.php uploaded to your server, be sure to remember to update
* "putUrl", "server", and "port" in the Remote Eyes code to match your server
* instead of "myexampleserver.com". See remote_eyes.html for the client HTML
* page that can be used to see what Remote Eyes is seeing.
*
* @author clchen@google.com (Charles L. Chen)
*/
public class RemoteEyesActivity extends Activity implements Callback {
private SurfaceHolder mHolder;
private String putUrl = "";
private String server = "";
private int port = 80;
private SurfaceView mPreview;
private Camera mCamera;
private boolean mTorchMode;
private HttpConnection mConnection;
private HttpState mHttpState;
private Rect r;
private int previewHeight = 0;
private int previewWidth = 0;
private int previewFormat = 0;
private boolean isUploading = false;
private byte[] mCallbackBuffer;
private ByteArrayOutputStream out;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
Log.e("remote eyes", "started");
super.onCreate(savedInstanceState);
mTorchMode = false;
out = new ByteArrayOutputStream();
if ((getIntent() != null) && (getIntent().getData() != null)) {
putUrl = getIntent().getData().toString();
server = putUrl.replace("http://", "");
server = server.substring(0, server.indexOf("/"));
Bundle extras = getIntent().getExtras();
if ((extras != null) && (extras.getBoolean("TORCH", false))) {
mTorchMode = true;
}
} else {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
putUrl = prefs.getString("REMOTE_EYES_PUT_URL", "");
Log.e("prefs", putUrl);
if (putUrl.length() < 1) {
Intent i = new Intent();
i.setClass(this, PrefsActivity.class);
startActivity(i);
finish();
return;
} else {
server = putUrl.replace("http://", "");
server = server.substring(0, server.indexOf("/"));
}
}
resetConnection();
mHttpState = new HttpState();
setContentView(R.layout.main);
mPreview = (SurfaceView) findViewById(R.id.preview);
mHolder = mPreview.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mPreview.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setTorchMode(!mTorchMode);
}
});
this.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
boolean useTorch = intent.getBooleanExtra("TORCH", false);
setTorchMode(useTorch);
}
}, new IntentFilter("android.intent.action.REMOTE_EYES_COMMAND"));
}
private void resetConnection() {
Log.e("server", server);
mConnection = new HttpConnection(server, port);
try {
mConnection.open();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
mHolder.setFixedSize(w, h);
// Start the preview
Parameters params = mCamera.getParameters();
previewHeight = params.getPreviewSize().height;
previewWidth = params.getPreviewSize().width;
previewFormat = params.getPreviewFormat();
// Crop the edges of the picture to reduce the image size
r = new Rect(100, 100, previewWidth - 100, previewHeight - 100);
mCallbackBuffer = new byte[460800];
mCamera.setParameters(params);
mCamera.setPreviewCallbackWithBuffer(new PreviewCallback() {
public void onPreviewFrame(byte[] imageData, Camera arg1) {
if (!isUploading) {
isUploading = true;
uploadImage(imageData);
}
}
});
mCamera.addCallbackBuffer(mCallbackBuffer);
mCamera.startPreview();
setTorchMode(mTorchMode);
}
private void uploadImage(byte[] imageData) {
try {
YuvImage yuvImage = new YuvImage(imageData, previewFormat, previewWidth, previewHeight,
null);
yuvImage.compressToJpeg(r, 20, out); // Tweak the quality here - 20
// seems pretty decent for
// quality + size.
PutMethod put = new PutMethod(putUrl);
put.setRequestBody(new ByteArrayInputStream(out.toByteArray()));
put.execute(mHttpState, mConnection);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
resetConnection();
} catch (ClientProtocolException e) {
e.printStackTrace();
resetConnection();
} catch (IOException e) {
e.printStackTrace();
resetConnection();
} finally {
out.reset();
if (mCamera != null) {
mCamera.addCallbackBuffer(mCallbackBuffer);
}
isUploading = false;
}
}
private void setTorchMode(boolean on) {
if (mCamera != null) {
Parameters params = mCamera.getParameters();
if (on) {
params.setFlashMode(Parameters.FLASH_MODE_TORCH);
} else {
params.setFlashMode(Parameters.FLASH_MODE_AUTO);
}
mTorchMode = on;
mCamera.setParameters(params);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, R.string.settings, 0, R.string.settings).setIcon(
android.R.drawable.ic_menu_preferences);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent intent;
switch (item.getItemId()) {
case R.string.settings:
intent = new Intent(this, PrefsActivity.class);
startActivity(intent);
finish();
break;
}
return super.onOptionsItemSelected(item);
}
}