/*
* Celljoust, a cellbot remote control program
*
* Copyright (C) 2010 All Things Geek LLC, portions may be copyright others as noted below
*
* 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.
*
* Contributors: darrell@allthingsgeek.com (Darrell Taylor), --author of CellJoust
* eric@allthingsgeek.com (Eric Hokanson) --author of ServoBot
* clchen@google.com (Charles L. Chen) --author of RemoteEye
* chaitanyag@google.com (Chaitanya Gharpure) --author Celldroid
*
*
* Portions of this code are based on RemoteEye
* RemoteEye is Apache Licensed and Copyright 2010 Google Inc.
*
* Portions of this code are based on Celldroid
* Celldroid is Apache Licensed and Copyright 2010 Google Inc.
*
*/
package com.allthingsgeek.celljoust;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import com.allthingsgeek.celljoust.R;
import com.cellbots.CellbotProtos.AudioVideoFrame;
import com.cellbots.CellbotProtos.PhoneState;
import com.cellbots.sensors.CompassManager;
import com.cellbots.sensors.LightSensorManager;
import com.cellbots.sensors.OrientationManager;
import com.cellbots.sensors.SensorListenerImpl;
import com.google.protobuf.ByteString;
import android.app.Activity;
import android.app.ProgressDialog;
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.SensorManager;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;
public class MainActivity extends Activity implements Callback
{
public static final String PREFS_NAME = "ServoBotPrefsFile";
private static final String TAG = "CellJoust";
PulseGenerator noise;
Movement mover;
private SurfaceHolder mHolder;
public static String putUrl = "";
private SurfaceView mPreview;
private Camera mCamera;
private boolean mTorchMode;
// private HttpState mHttpState;
private Rect r;
private int previewHeight = 0;
private int previewWidth = 0;
private int previewFormat = 0;
//how much to crop the edges
private int previewShrink = 0;
private int jpegCompressionLevel = 20;
private byte[] mCallbackBuffer;
byte[] buff;
private ByteArrayOutputStream out;
private ConversionWorker convWorker;
public static SensorManager sensorManager;
RobotStateHandler state;
WifiManager wifiManager;
SensorListenerImpl sensorListener;
public boolean sendVideoFrames = true;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ServoOn");
// wl.acquire();
// wl.release();
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
noise = PulseGenerator.getInstance();
mover = Movement.getInstance();
loadPrefs();
mTorchMode = false;
out = new ByteArrayOutputStream();
setContentView(R.layout.main);
if (sensorManager == null)
{
sensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);
}
startListening();
mPreview = (SurfaceView) findViewById(R.id.preview);
mHolder = mPreview.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
noise.pause();
}
private void loadPrefs()
{
// Restore preferences
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
putUrl = settings.getString("REMOTE_EYES_PUT_URL", "http://example.com:8080/cellserv");
noise.setOffsetPulsePercent(settings.getInt("servo1Percent", 50), 0);
noise.setOffsetPulsePercent(settings.getInt("servo2Percent", 50), 1);
noise.setOffsetPulsePercent(settings.getInt("servo3Percent", 50), 2);
noise.setOffsetPulsePercent(settings.getInt("servo4Percent", 50), 3);
mover.setOffset(settings.getInt("wheelOffset", 0));
RobotStateHandler.ROBOT_ID = settings.getString("ROBOT_ID", RobotStateHandler.ROBOT_ID);
}
@Override
public void onResume()
{
loadPrefs();
super.onResume();
}
private Handler handler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
// utterTaunt((String) msg.obj);
if (msg.obj instanceof PhoneState)
{
// this.state = msg.obj;
}
}
};
private synchronized void startListening()
{
Log.d(TAG, "startListening called");
convWorker = new ConversionWorker();
if (state == null)
{
state = new RobotStateHandler(handler);
state.start();
while(state.handler == null)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
}
}
}
if (sensorListener == null)
{
sensorListener = new SensorListenerImpl(state.handler,wifiManager);
}
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
RobotStateHandler.ROBOT_ID = settings.getString("ROBOT_ID", RobotStateHandler.ROBOT_ID);
// Toast.makeText(CONTEXT, "Current IP:" + state.getLocalIpAddress(),
// Toast.LENGTH_LONG);
// ProgressDialog.show(me, msg,
// "Searching for a Bluetooth serial port...");
ProgressDialog btDialog = null;
String connectivity_context = Context.WIFI_SERVICE;
WifiManager wifi = (WifiManager) getSystemService(connectivity_context);
this.registerReceiver(sensorListener.mBatInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
this.registerReceiver(sensorListener.mWifiInfoReceiver, new IntentFilter(WifiManager.RSSI_CHANGED_ACTION));
if (OrientationManager.isSupported())
{
OrientationManager.startListening(sensorListener);
}
if (LightSensorManager.isSupported())
{
LightSensorManager.startListening(sensorListener);
}
if (CompassManager.isSupported())
{
CompassManager.startListening(sensorListener);
}
}
private synchronized void stopListening()
{
Log.d(TAG, "stopListening called");
convWorker.kill();
try
{
this.unregisterReceiver(sensorListener.mBatInfoReceiver);
}
catch (Exception e)
{
}
try
{
this.unregisterReceiver(sensorListener.mWifiInfoReceiver);
}
catch (Exception e)
{
}
OrientationManager.stopListening();
LightSensorManager.stopListening();
CompassManager.stopListening();
// if (state.isAlive())
// {
// state.stopListening();
// }
}
protected void onDestroy()
{
//noise.stop();
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
// Handle item selection
switch (item.getItemId())
{
case R.id.setup:
Intent i = new Intent(this, SetupActivity.class);
startActivity(i);
break;
case R.id.quit:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
return mover.processKeyDownEvent(keyCode) != null;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
return mover.processKeyUpEvent(keyCode);
}
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;
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(previewShrink, previewShrink, previewWidth - previewShrink, previewHeight - previewShrink);
mCallbackBuffer = new byte[497664];
mCamera.setParameters(params);
mCamera.setPreviewCallbackWithBuffer(new PreviewCallback()
{
public void onPreviewFrame(byte[] imageData, Camera arg1)
{
convWorker.nextFrame(imageData);
}
});
mCamera.addCallbackBuffer(mCallbackBuffer);
mCamera.startPreview();
setTorchMode(mTorchMode);
}
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);
}
}
class ConversionWorker extends Thread
{
// private HttpConnection mConnection;
HttpClient httpclient;
boolean alive;
volatile HttpPost post;
volatile boolean sending = false;
public ConversionWorker()
{
// setDaemon(true);
// this client should automatically reuse its connection
httpclient = new DefaultHttpClient();
alive = true;
start();
}
public void kill()
{
alive = false;
this.notify();
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public synchronized void run()
{
try
{
wait();// wait for initial frame
}
catch (InterruptedException e)
{
}
while (alive && sendVideoFrames)
{
try
{
httpclient = new DefaultHttpClient();
sending = true;
YuvImage yuvImage = new YuvImage(mCallbackBuffer, previewFormat, previewWidth, previewHeight, null);
yuvImage.compressToJpeg(r, jpegCompressionLevel, out); // Tweak the
// quality here
// state.setVideoFrame(ByteString.copyFrom(out.toByteArray()));
AudioVideoFrame.Builder avFrame = AudioVideoFrame.newBuilder();
avFrame.setData(ByteString.copyFrom(out.toByteArray()));
avFrame.setBotID(RobotStateHandler.ROBOT_ID);
avFrame.setCompressionLevel(jpegCompressionLevel);
//FIXME: need to be able to change url
post = new HttpPost("http://"+putUrl + "/video");
post.setEntity(new ByteArrayEntity(avFrame.build().toByteArray()));
// Log.i(TAG, "sending video");
httpclient.execute(post);
sending = false;
// Log.i(TAG, "sent video");
// InputStream resStream = resp.getEntity().getContent();
// ControllerState cs = ControllerState.parseFrom(resStream);
// mover.processControllerStateEvent(cs);
}
catch (UnsupportedEncodingException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalStateException e)
{
e.printStackTrace();
}
catch (com.google.protobuf.InvalidProtocolBufferException e)
{
// e.printStackTrace();
// resetConnection();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
out.reset();
if (mCamera != null)
{
mCamera.addCallbackBuffer(mCallbackBuffer);
}
sending = false;
// isUploading = false;
}
try
{
wait();// wait for next frame
}
catch (InterruptedException e)
{
}
}
}
synchronized boolean nextFrame(byte[] frame)
{
if (this.getState() == Thread.State.WAITING && !sending)
{
// ok, we are ready for a new frame:
// curFrame = frame;
// do the work:
this.notify();
return true;
}
else
{
// ignore it
return false;
}
}
}
}