Java tutorial
/** * * Copyright (c) 2014 Billy Wood * This file is part of Indi. * * Indi is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Indi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Indi. If not, see <http://www.gnu.org/licenses/>. * */ package com.DorsetEggs.waver; import android.app.IntentService; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.ParcelFileDescriptor; import android.support.v4.app.NotificationCompat; import android.telephony.TelephonyManager; import android.util.Log; import android.widget.TextView; import android.widget.Toast; import com.android.future.usb.UsbAccessory; import com.android.future.usb.UsbManager; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Vector; public class communicatorService extends IntentService { public enum ServoTypes { DLARM, ULARM, DRARM, URARM, NUMOFSERVOS } private static final int MAX_SERVO_ROTATION = 180; //Notification variables NotificationCompat.Builder mBuilder; int mId = 29; NotificationManager mNotificationManager; static boolean active = false; class KeyframePacket { public byte/*uint8_t*/ servoToApplyTo; public byte/*uint8_t*/ degreesToReach; // 0 to 180, 0 is straight down //The max amount of time that an animation keyframe can take is 65 seconds. //We can up it later if we feel like it by upping these variables to uint32_t public short/*uint16_t*/ timeToPosition;//Time in ms to reach this point } ; //2D vector [Servo][Keyframe] Vector<Vector<KeyframePacket>> servoAnimations = new Vector<Vector<KeyframePacket>>(); Vector<KeyframePacket> defaultServoPositions = new Vector<KeyframePacket>(); // The default positions for the motors byte[] servoAnimationInPlay = new byte[ServoTypes.NUMOFSERVOS.ordinal()]; public static final boolean D = BuildConfig.DEBUG; // This is automatically set when building private static final String TAG = "IndiActivity"; // TAG is used to debug in Android logcat console private static final String ACTION_USB_PERMISSION = "com.DorsetEggs.arduino.waver.robot.USB_PERMISSION"; UsbAccessory mAccessory; ParcelFileDescriptor mFileDescriptor; FileInputStream mInputStream; FileOutputStream mOutputStream; private UsbManager mUsbManager; private PendingIntent mPermissionIntent; private boolean mPermissionRequestPending; boolean callOngoing; boolean sendReset = false; ConnectedThread mConnectedThread; SQLiteDatabase db; /** * A constructor is required, and must call the super IntentService(String) * constructor with a name for the worker thread. */ public communicatorService() { super("communicatorService"); } @Override public void onCreate() { super.onCreate(); sendDebugMessage("App is up"); } /** * The IntentService calls this method from the default worker thread with * the intent that started the service. When this method returns, IntentService * stops the service, as appropriate. */ @Override protected void onHandleIntent(Intent intent) { //Using the the flag to decide if this is a singleton sendDebugMessage("Intent handled, active == true"); /*if(!active)*/ { //android.os.Debug.waitForDebugger(); callOngoing = false; sendDebugMessage("Waiting for connection"); instantiateAnimation(); mUsbManager = UsbManager.getInstance(this); mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter usbFilter = new IntentFilter(ACTION_USB_PERMISSION); usbFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED); registerReceiver(mUsbReceiver, usbFilter); IntentFilter callFilter = new IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED); registerReceiver(mCallReceiver, callFilter); if (mAccessory != null) { setConnectionStatus(true); return; } UsbAccessory[] accessories = mUsbManager.getAccessoryList(); UsbAccessory accessory = (accessories == null ? null : accessories[0]); if (accessory != null) { sendDebugMessage("USB ready"); if (mUsbManager.hasPermission(accessory)) { openAccessory(accessory); active = true; launchNotification(mId, "Indi connected"); resetMotors(); //Initialise the database SQLiteDatabase db = globals.dbHelper.getWritableDatabase(); } else { setConnectionStatus(false); synchronized (mUsbReceiver) { if (!mPermissionRequestPending) { mUsbManager.requestPermission(accessory, mPermissionIntent); mPermissionRequestPending = true; } } } } else { setConnectionStatus(false); if (D) sendDebugMessage("mAccessory is null"); } // Play here until the phone is disconnected while (true) ; } } private void launchNotification(int id, String notificationText) { if (mNotificationManager == null) { mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notification2).setContentTitle(notificationText); NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); mBuilder.setStyle(inboxStyle); mNotificationManager.notify(id, mBuilder.build()); } //Wrapper class for the below 'transmitKeyFrame' private void sendKeyframe(byte servo, int keyframe) { transmitKeyFrame(servoAnimations.get(servo).get(keyframe)); sendDebugMessage("Keyframe sent -/nServo = " + servo + "/nkeyframe = " + keyframe); } private void transmitKeyFrame(KeyframePacket keyframePacket) { sendDebugMessage("Entering function" + Thread.currentThread().getStackTrace()); if (mOutputStream != null) { try { byte[] byteArray = toByteArray(keyframePacket); mOutputStream.write(byteArray); } catch (IOException e) { if (D) { Log.e(TAG, "write failed", e); } } } } private void instantiateAnimation() { //Load the default position for (byte i = 0; i < ServoTypes.NUMOFSERVOS.ordinal(); ++i) { KeyframePacket keyframe = new KeyframePacket(); keyframe.servoToApplyTo = i; keyframe.degreesToReach = 0; keyframe.timeToPosition = 1000; defaultServoPositions.add(keyframe); } //Load the default waving animation, kept here for debugging purposes /*for(byte i = 0; i < ServoTypes.NUMOFSERVOS.ordinal(); ++i) { Vector<KeyframePacket> servoAnimation = new Vector<KeyframePacket>(); KeyframePacket keyframe1 = new KeyframePacket(); KeyframePacket keyframe2 = new KeyframePacket(); keyframe1.servoToApplyTo = i; keyframe1.degreesToReach = 90; keyframe1.timeToPosition = 1000; servoAnimation.add(keyframe1); keyframe2.servoToApplyTo = i; keyframe2.degreesToReach = 0; keyframe2.timeToPosition = 1000; servoAnimation.add(keyframe2); servoAnimations.add(i, servoAnimation); }*/ resetServoAnimations(); } private void resetServoAnimations() { for (byte i = 0; i < ServoTypes.NUMOFSERVOS.ordinal(); ++i) { transmitKeyFrame(defaultServoPositions.elementAt(i)); } } public static byte[] toByteArray(KeyframePacket obj) throws IOException { ByteBuffer bytes = ByteBuffer.allocate(4); bytes.order(ByteOrder.LITTLE_ENDIAN); bytes.put(obj.servoToApplyTo); bytes.put(obj.degreesToReach); bytes.putShort(obj.timeToPosition); return bytes.array(); } private void animationCompleteReceived(byte servo) { if (true == callOngoing) { servoAnimationInPlay[servo] += 1; if (servoAnimationInPlay[servo] >= servoAnimations.get(servo).size()) { servoAnimationInPlay[servo] = 0; } sendKeyframe(servo, servoAnimationInPlay[servo]); sendReset = true; } else if (sendReset) //If the call is disconnected, send a signal to reset the motors once { resetMotors(); sendReset = false; } sendDebugMessage("Animation complete recieved"); } public void resetMotors() { //Make sure the servos are in the default position for (byte i = 0; i < ServoTypes.NUMOFSERVOS.ordinal(); ++i) { transmitKeyFrame(defaultServoPositions.elementAt(i)); } } public void sendDebugMessage(String text) { launchNotification(mId, text); Log.d(TAG, text); } @Override public void onDestroy() { super.onDestroy(); closeAccessory(); unregisterReceiver(mUsbReceiver); unregisterReceiver(mCallReceiver); } private void setConnectionStatus(boolean connected) { //exit when disconnected if (connected == false) { sendDebugMessage("Exiting due to disconnection"); if (mNotificationManager != null) { mNotificationManager.cancel(mId); } active = false; stopSelf(); } } private void openAccessory(UsbAccessory accessory) { mFileDescriptor = mUsbManager.openAccessory(accessory); if (mFileDescriptor != null) { mAccessory = accessory; FileDescriptor fd = mFileDescriptor.getFileDescriptor(); mInputStream = new FileInputStream(fd); mOutputStream = new FileOutputStream(fd); mConnectedThread = new ConnectedThread(); mConnectedThread.start(); setConnectionStatus(true); if (D) sendDebugMessage("Accessory opened"); } else { setConnectionStatus(false); if (D) sendDebugMessage("Accessory open failed"); } } private void closeAccessory() { setConnectionStatus(false); // Cancel any thread currently running a connection if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } // Close all streams try { if (mInputStream != null) mInputStream.close(); } catch (Exception ignored) { } finally { mInputStream = null; } try { if (mOutputStream != null) mOutputStream.close(); } catch (Exception ignored) { } finally { mOutputStream = null; } try { if (mFileDescriptor != null) mFileDescriptor.close(); } catch (IOException ignored) { } finally { mFileDescriptor = null; mAccessory = null; } } private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { @Override //Usb broadcast event public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_USB_PERMISSION.equals(action)) { synchronized (this) { UsbAccessory accessory = UsbManager.getAccessory(intent); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { openAccessory(accessory); } else { if (D) sendDebugMessage("Permission denied for accessory " + accessory); } mPermissionRequestPending = false; } } else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) { UsbAccessory accessory = UsbManager.getAccessory(intent); if (accessory != null && accessory.equals(mAccessory)) closeAccessory(); } } }; private class ConnectedThread extends Thread { TextView mTextView; byte[] buffer = new byte[1024]; boolean running; ConnectedThread() { running = true; } public void run() { while (running) { try { int bytes = mInputStream.read(buffer); if (bytes > 0) { // The message is 1 bytes long byte servo = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).get(); animationCompleteReceived(servo); } } catch (Exception ignore) { } } } public void cancel() { running = false; } } private final BroadcastReceiver mCallReceiver = new BroadcastReceiver() { @Override //Call broadcast event public void onReceive(Context context, Intent intent) { //Call broadcast event if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_RINGING)) { // This code will execute when the phone has an incoming call // get the phone number String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); Log.e(TAG, "Call from:" + incomingNumber); callOngoing = true; loadAnimation(globals.animOptions.CALL_RECIEVED.ordinal()); for (byte i = 0; i < ServoTypes.NUMOFSERVOS.ordinal(); ++i) { sendKeyframe(i, 0); } } else if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_IDLE) || intent.getStringExtra(TelephonyManager.EXTRA_STATE) .equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) { Log.e(TAG, "Detected call hangup event"); callOngoing = false; resetServoAnimations(); } } }; private void loadAnimation(int animOption) { //First, load the animation to use String[] projectionAnimToActions = { globals.AnimToActions.COLUMN_NAME_ACTION, globals.AnimToActions.COLUMN_NAME_ANIMATION, }; String sortOrderAnimToActions = globals.AnimToActions.COLUMN_NAME_ACTION + " DESC"; Cursor cAnimToActions = db.query(globals.AnimToActions.TABLE_NAME, // The table to query projectionAnimToActions, // The columns to return globals.AnimToActions.COLUMN_NAME_ACTION + " = " + animOption, // The columns for the WHERE clause null, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrderAnimToActions // The sort order ); if (cAnimToActions.getColumnCount() == 0) { Toast.makeText(this, "Animation not found for action", Toast.LENGTH_LONG).show(); } //Next load the selected animation String[] projectionAnimations = { globals.Animations.COLUMN_NAME_POSITION, globals.Animations.COLUMN_NAME_TIME, }; String sortOrderAnimations = globals.Animations.COLUMN_NAME_KEYFRAME + " DESC"; //Finally place the animation into the array for later processing for (Integer i = 0; i < ServoTypes.NUMOFSERVOS.ordinal(); ++i) { String whereClause = globals.Animations.COLUMN_NAME_TITLE + " = " + cAnimToActions .getString(cAnimToActions.getColumnIndex(globals.AnimToActions.COLUMN_NAME_ANIMATION)) + " AND " + globals.Animations.COLUMN_NAME_MOTOR + " = " + i.toString(); Cursor cAnimations = db.query(globals.Animations.TABLE_NAME, // The table to query projectionAnimations, // The columns to return whereClause, // The columns for the WHERE clause null, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrderAnimations // The sort order ); if (cAnimations.getColumnCount() == 0) { Toast.makeText(this, "Animation not found", Toast.LENGTH_LONG).show(); } if (cAnimations != null) { if (cAnimations.moveToFirst()) { int positionColumn = cAnimations.getColumnIndex(globals.Animations.COLUMN_NAME_POSITION); int timeColumn = cAnimations.getColumnIndex(globals.Animations.COLUMN_NAME_TIME); Vector<KeyframePacket> servoAnimation = new Vector<KeyframePacket>(); do { KeyframePacket keyframe = new KeyframePacket(); keyframe.servoToApplyTo = i.byteValue(); keyframe.degreesToReach = (byte) cAnimations.getInt(positionColumn); keyframe.timeToPosition = (short) cAnimations.getInt(timeColumn); servoAnimation.add(keyframe); } while (cAnimations.moveToNext()); servoAnimations.add(i, servoAnimation); } } } } }