package com.integreight.onesheeld.shields;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.Toast;
import com.google.android.gms.analytics.HitBuilders;
import com.integreight.onesheeld.MainActivity;
import com.integreight.onesheeld.OneSheeldApplication;
import com.integreight.onesheeld.R;
import com.integreight.onesheeld.enums.ArduinoPin;
import com.integreight.onesheeld.enums.UIShield;
import com.integreight.onesheeld.model.ArduinoConnectedPin;
import com.integreight.onesheeld.sdk.OneSheeldDataCallback;
import com.integreight.onesheeld.sdk.OneSheeldDevice;
import com.integreight.onesheeld.sdk.ShieldFrame;
import com.integreight.onesheeld.shields.controller.TaskerShield;
import com.integreight.onesheeld.utils.AppShields;
import com.integreight.onesheeld.utils.CrashlyticsUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
/**
* @param <T> the Child class used for casting
* <p/>
* Is the parent class for all shields controllers
* @author Ahmed Saad
*/
@SuppressWarnings("unchecked")
public abstract class ControllerParent<T extends ControllerParent<?>> {
private static final byte IS_SHIELD_SELECTED = (byte) 0xFF;
private static final byte SELECT_SHIELD = (byte) 0xFE;
private static final byte DESELECT_SHIELD = (byte) 0xFD;
public MainActivity activity;
protected List<String> requiredPermissions = new ArrayList<String>();
// case of FALSE
public String[][] requiredPinsNames = new String[][]{
{"2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13",
"A0", "A1", "A2", "A3", "A4", "A5"},
{"3", "5", "6", "9", "10", "11"}}; // 2D array for pins types
// (Digital and PWM)
public Hashtable<String, ArduinoPin> matchedShieldPins = new Hashtable<String, ArduinoPin>(); // saving
// connected
// pins
public int requiredPinsIndex = -1;// related to the pins type (index of the
// disconnection or disselect)
public String[] shieldPins = new String[]{};// Specific shield pins names
// (like "Toggle" pin with
// ON/Off shield`)
public SelectionAction selectionAction;// interface implemented in case of
// shields that may be unavailable
// like not
// found sensors
public boolean isInteractive = true;// flag used for the interaction
public List<String> permissions = new ArrayList<String>();
// top-right toggle button
public boolean cachedArduinoCallbackStatus = false;
public Handler actionHandler = new Handler(); // queuing sysex UI calls
public Handler onDigitalActionHandler = new Handler(); // Queuing digital UI
private boolean hasConnectedPins = false; // flag for detecting the
// connected pins to stop the
// analog and digital effects
private String tag = ""; // Shield identical key
private boolean hasForgroundView = false;// flag to stop UI handler calls in
// requiredPinsNames array)
private boolean isALive = false; // flag for fired shields (in case of
// Interface implemented for listening to Arduino actions
private long selectionTime;
private boolean isInit = false;
// Interface implemented for listening to received Shields Frames
public OneSheeldDataCallback dataCallback = new OneSheeldDataCallback() {
private int portNumber;
private boolean portStatus;
// UI runnable to be called onDigital actions
Runnable onDigitalRunnable = new Runnable() {
@Override
public void run() {
if (hasConnectedPins
|| ((T) ControllerParent.this) instanceof TaskerShield)
((T) ControllerParent.this).onDigital(portNumber, portStatus);
}
};
@Override
public void onDigitalPinStatusChange(OneSheeldDevice device, int pinNumber, boolean newValue) {
super.onDigitalPinStatusChange(device, pinNumber, newValue);
if (isALive && isInteractive) {
onDigitalActionHandler.removeCallbacks(onDigitalRunnable);
this.portStatus = newValue;
this.portNumber = pinNumber;
onDigitalActionHandler.post(onDigitalRunnable);
}
}
@Override
public void onSerialDataReceive(OneSheeldDevice device, int data) {
super.onSerialDataReceive(device, data);
}
@Override
public void onShieldFrameReceive(OneSheeldDevice device, final ShieldFrame frame) {
super.onShieldFrameReceive(device, frame);
if (isALive && frame != null && matchedShieldPins.size() == 0 && isInit)
if (frame.getShieldId() == getShieldId())
if (frame.getFunctionId() == IS_SHIELD_SELECTED)
notifyHardwareOfShieldSelection();
else if (frame.getFunctionId() == SELECT_SHIELD) {
} else if (frame.getFunctionId() == DESELECT_SHIELD) {
} else {
cachedArduinoCallbackStatus = device.isArduinoInACallback();
actionHandler.post(new Runnable() {
@Override
public void run() {
try {
if (isInteractive)
((T) ControllerParent.this)
.onNewShieldFrameReceived(frame);
cachedArduinoCallbackStatus = false;
} catch (RuntimeException e) {
cachedArduinoCallbackStatus = false;
Toast.makeText(getActivity(), R.string.general_toasts_received_an_unexpected_frame_toast, Toast.LENGTH_SHORT).show();
CrashlyticsUtils.logException(e);
}
}
});
}
}
};
public ControllerParent() {
// TODO Auto-generated constructor stub
tag = AppShields.getInstance().getShieldTag(((T) (this)).getClass().getName());
}
/**
* @param activity MainActivity instance
* @param tag Shield unique name gotten from UIShield ENUM
*/
public ControllerParent(Activity activity, String tag) {
setActivity((MainActivity) activity);
init(tag);
}
/**
* @param activity MainActivity instance
* @param tag Shield unique name gotten from UIShield ENUM
*/
public ControllerParent(Activity activity, String tag, boolean manageShieldSelectionFrameManually) {
setActivity((MainActivity) activity);
init(tag, manageShieldSelectionFrameManually);
}
public void notifyHardwareOfShieldSelection() {
if (isItARealShield() && getApplication().isConnectedToBluetooth()) {
getApplication().getConnectedDevice().sendShieldFrame(new ShieldFrame(getShieldId(), IS_SHIELD_SELECTED), true);
}
isInit = true;
}
/**
* used to set selected pin mode
*
* @param pins array of connected pins
*/
public void setConnected(ArduinoConnectedPin... pins) {
for (int i = 0; i < pins.length; i++) {
if (getApplication().isConnectedToBluetooth())
getApplication().getConnectedDevice().pinMode(pins[i].getPinID(), pins[i].getPinMode());
}
this.setHasConnectedPins(true);
}
/**
* @return Shield visibility status to allow UI interaction or not
*/
public boolean isHasForgroundView() {
return hasForgroundView;
}
/**
* changing shield UI status
*
* @param hasForgroundView true -- onStart | false -- onStop
*/
public void setHasForgroundView(boolean hasForgroundView) {
this.hasForgroundView = hasForgroundView;
if (hasForgroundView) {
((T) ControllerParent.this).refresh();
if (getActivity() != null
&& getActivity().findViewById(R.id.pinsFixedHandler) != null)
getActivity().findViewById(R.id.pinsFixedHandler)
.setVisibility(
requiredPinsIndex != -1 ? View.VISIBLE
: View.GONE);
}
}
/**
* @return activity instance
*/
public Activity getActivity() {
return activity;
}
/**
* @param activity
* @return instance of Shield ControllerParent for reflection usage
*/
public ControllerParent<T> setActivity(MainActivity activity) {
this.activity = activity;
setFirmataEventHandler();
return this;
}
public void updateActivty(MainActivity activity) {
this.activity = activity;
}
public void refresh() {
}
protected void addRequiredPremission(String premission) {
requiredPermissions.add(premission);
}
public void onSysex(byte command, byte[] data) {
// TODO Auto-generated method stub
}
public void onDigital(int portNumber, boolean portData) {
}
// calls
public void onAnalog(int pin, int value) {
// TODO Auto-generated method stub
}
public abstract void onNewShieldFrameReceived(ShieldFrame frame);
/**
* add Shield handlers to firmata listening list
*/
private void setFirmataEventHandler() {
if (getApplication().isConnectedToBluetooth())
getApplication().getConnectedDevice().addDataCallback(dataCallback);
}
private byte getShieldId() {
if (ControllerParent.this instanceof TaskerShield) return UIShield.TASKER_SHIELD.getId();
return AppShields.getInstance().getShield(tag).id;
}
public OneSheeldApplication getApplication() {
return activity.getThisApplication();
}
public String getTag() {
return tag;
}
/**
* @param tag unique shield name
* @return instance of shield controller for Java reflection usage
* (initialization)
*/
public ControllerParent<T> init(String tag) {
return init(tag, false);
}
/**
* @param tag unique shield name
* @return instance of shield controller for Java reflection usage
* (initialization)
*/
public ControllerParent<T> init(String tag, boolean manageShieldSelectionFrameManually) {
this.tag = tag;
isALive = true;
if (getApplication().getRunningShields().get(tag) == null)
getApplication().getRunningShields().put(tag, this);
selectionTime = SystemClock.elapsedRealtime();
CrashlyticsUtils
.setString(
"Number of running shields",
getApplication().getRunningShields() == null
|| getApplication().getRunningShields().size() == 0 ? "No Running Shields"
: ""
+ getApplication().getRunningShields()
.size());
CrashlyticsUtils
.setString(
"Running Shields",
getApplication().getRunningShields() != null
&& getApplication().getRunningShields().size() > 0 ? getApplication()
.getRunningShields().keySet().toString()
: "No Running Shields");
if (!manageShieldSelectionFrameManually) notifyHardwareOfShieldSelection();
return this;
}
/**
* @param selectionAction implemented interface to do on validation
* @param isToastable allow showing toasts or UI Message, related to selectAll or
* select single shield
* @return instance of shield controller for Java reflection usage
* (initialization)
*/
public ControllerParent<T> invalidate(SelectionAction selectionAction,
boolean isToastable) {
this.selectionAction = selectionAction;
return this;
}
public boolean checkForPermissions() {
for (String perm : requiredPermissions) {
if ((ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED))
permissions.add(perm);
}
if (permissions.size() > 0) {
ActivityCompat.requestPermissions(activity, permissions.toArray(new String[permissions.size()]), MainActivity.PREMISSION_REQUEST_CODE);
return false;
// // Should we show an explanation?
// if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
// Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(activity,
// Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
//
// // Show an expanation to the user *asynchronously* -- don't block
// // this thread waiting for the user's response! After the user
// // sees the explanation, try again to request the permission.
// ActivityCompat.requestPermissions(activity, permissions.toArray(new String[permissions.size()]), MainActivity.PREMISSION_REQUEST_CODE);
// } else {
//
// // No explanation needed, we can request the permission.
// ActivityCompat.requestPermissions(activity, permissions.toArray(new String[permissions.size()]), MainActivity.PREMISSION_REQUEST_CODE);
// }
} else {
return true;
}
// return false;
}
public boolean isHasConnectedPins() {
return hasConnectedPins;
}
public void setHasConnectedPins(boolean hasConnectedPins) {
this.hasConnectedPins = hasConnectedPins;
}
private boolean isItARealShield() {
return !(ControllerParent.this instanceof TaskerShield);
}
/**
* Called on shield firing process
*/
public void resetThis() {
if (activity != null) {
if (activity.looperThread == null
|| (!activity.looperThread.isAlive() || activity.looperThread
.isInterrupted()))
activity.initLooperThread();
isALive = false;
activity.backgroundThreadHandler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
if (matchedShieldPins != null)
matchedShieldPins.clear();
if (shieldPins != null && shieldPins.length > 0) {
for (ArduinoPin pin : Arrays.asList(ArduinoPin.values())) {
for (int i = 0; i < shieldPins.length; i++) {
if (pin.connectedPins.size() == 0)
break;
pin.connectedPins
.remove(((T) ControllerParent.this)
.getClass().getName()
+ shieldPins[i]);
}
}
}
((T) ControllerParent.this).reset();
if (getApplication().getConnectedDevice() != null)
getApplication().getConnectedDevice().removeDataCallback(dataCallback);
}
});
}
if (selectionTime != 0 && !(((T) ControllerParent.this) instanceof TaskerShield)) {
getApplication().getTracker().send(new HitBuilders.TimingBuilder()
.setCategory("Shields Selection Timing")
.setValue(SystemClock.elapsedRealtime() - selectionTime)
.setVariable(tag)
.build());
selectionTime = 0;
}
CrashlyticsUtils
.setString(
"Number of running shields",
getApplication().getRunningShields() == null
|| getApplication().getRunningShields().size() == 0 ? "No Running Shields"
: ""
+ getApplication().getRunningShields()
.size());
CrashlyticsUtils
.setString(
"Running Shields",
getApplication().getRunningShields() != null
&& getApplication().getRunningShields().size() > 0 ? getApplication()
.getRunningShields().keySet().toString()
: "No Running Shields");
cachedArduinoCallbackStatus = false;
}
/**
* @param frame target frame sending frame to Arduino Onesheeld
*/
public void queueShieldFrame(ShieldFrame frame) {
if (isInteractive && getApplication().isConnectedToBluetooth())
getApplication().getConnectedDevice().queueShieldFrame(frame);
}
/**
* @param frame target frame sending frame to Arduino Onesheeld
*/
public void sendShieldFrame(ShieldFrame frame) {
if (isInteractive && getApplication().isConnectedToBluetooth())
getApplication().getConnectedDevice().sendShieldFrame(frame);
}
/**
* @param frame target frame queuing frame to Arduino Onesheeld
*/
public void sendShieldFrame(ShieldFrame frame, boolean waitIfInACallback) {
if (isInteractive && getApplication().isConnectedToBluetooth()) {
getApplication().getConnectedDevice().sendShieldFrame(frame, waitIfInACallback);
}
}
public void digitalWrite(int pin, boolean value) {
if (isInteractive && getApplication().isConnectedToBluetooth())
getApplication().getConnectedDevice().digitalWrite(pin, value);
}
public void analogWrite(int pin, int value) {
if (isInteractive && getApplication().isConnectedToBluetooth())
getApplication().getConnectedDevice().analogWrite(pin, value);
}
/**
* abstract implemented in child class to be called on firing the Shield
*/
public abstract void reset();
public void preConfigChangeThis() {
((T) ControllerParent.this).preConfigChange();
}
public void postConfigChangeThis() {
((T) ControllerParent.this).postConfigChange();
}
public void preConfigChange() {
}
public void postConfigChange() {
this.activity = (MainActivity) getActivity();
}
public String[] getRequiredPinsNames() {
return requiredPinsNames[requiredPinsIndex];
}
/**
* @author Ahmed Saad Interface used to Invalidate specific shield on select
*/
public static interface SelectionAction {
public void onSuccess();
public void onFailure();
}
}