/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.stage;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.util.Log;
import org.catrobat.catroid.CatroidApplication;
import org.catrobat.catroid.ProjectManager;
import org.catrobat.catroid.R;
import org.catrobat.catroid.bluetooth.base.BluetoothDevice;
import org.catrobat.catroid.bluetooth.base.BluetoothDeviceService;
import org.catrobat.catroid.camera.CameraManager;
import org.catrobat.catroid.common.CatroidService;
import org.catrobat.catroid.common.Constants;
import org.catrobat.catroid.common.ServiceProvider;
import org.catrobat.catroid.content.Scene;
import org.catrobat.catroid.content.Sprite;
import org.catrobat.catroid.content.bricks.Brick;
import org.catrobat.catroid.devices.raspberrypi.RaspberryPiService;
import org.catrobat.catroid.drone.DroneInitializer;
import org.catrobat.catroid.drone.DroneServiceWrapper;
import org.catrobat.catroid.facedetection.FaceDetectionHandler;
import org.catrobat.catroid.formulaeditor.SensorHandler;
import org.catrobat.catroid.sensing.GatherCollisionInformationTask;
import org.catrobat.catroid.ui.BaseActivity;
import org.catrobat.catroid.ui.SettingsActivity;
import org.catrobat.catroid.ui.dialogs.CustomAlertDialogBuilder;
import org.catrobat.catroid.ui.dialogs.NoNetworkDialog;
import org.catrobat.catroid.utils.FlashUtil;
import org.catrobat.catroid.utils.ToastUtil;
import org.catrobat.catroid.utils.TouchUtil;
import org.catrobat.catroid.utils.Utils;
import org.catrobat.catroid.utils.VibratorUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@SuppressWarnings("deprecation")
public class PreStageActivity extends BaseActivity implements GatherCollisionInformationTask.OnPolygonLoadedListener {
private static final String TAG = PreStageActivity.class.getSimpleName();
private static final int REQUEST_CONNECT_DEVICE = 1000;
public static final int REQUEST_RESOURCES_INIT = 101;
public static final int REQUEST_TEXT_TO_SPEECH = 10;
public static final int REQUEST_GPS = 1;
private int requiredResourceCounter;
private Set<Integer> failedResources;
private static TextToSpeech textToSpeech;
private static OnUtteranceCompletedListenerContainer onUtteranceCompletedListenerContainer;
private DroneInitializer droneInitializer = null;
private Intent returnToActivityIntent = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
returnToActivityIntent = new Intent();
if (isFinishing()) {
return;
}
setContentView(R.layout.activity_prestage);
TouchUtil.reset();
int requiredResources = ProjectManager.getInstance().getCurrentProject().getRequiredResources();
requiredResourceCounter = Integer.bitCount(requiredResources);
failedResources = new HashSet<>();
SensorHandler sensorHandler = SensorHandler.getInstance(getApplicationContext());
if ((requiredResources & Brick.SENSOR_ACCELERATION) != 0) {
if (sensorHandler.accelerationAvailable()) {
resourceInitialized();
} else {
resourceFailed(Brick.SENSOR_ACCELERATION);
}
}
if ((requiredResources & Brick.SENSOR_INCLINATION) != 0) {
if (sensorHandler.inclinationAvailable()) {
resourceInitialized();
} else {
resourceFailed(Brick.SENSOR_INCLINATION);
}
}
if ((requiredResources & Brick.SENSOR_COMPASS) != 0) {
if (sensorHandler.compassAvailable()) {
resourceInitialized();
} else {
resourceFailed(Brick.SENSOR_COMPASS);
}
}
if ((requiredResources & Brick.SENSOR_GPS) != 0) {
if (SensorHandler.gpsAvailable()) {
resourceInitialized();
} else {
Intent checkIntent = new Intent();
checkIntent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivityForResult(checkIntent, REQUEST_GPS);
}
}
if ((requiredResources & Brick.TEXT_TO_SPEECH) != 0) {
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, REQUEST_TEXT_TO_SPEECH);
}
if ((requiredResources & Brick.BLUETOOTH_LEGO_NXT) != 0) {
connectBTDevice(BluetoothDevice.LEGO_NXT);
}
if ((requiredResources & Brick.BLUETOOTH_LEGO_EV3) != 0) {
connectBTDevice(BluetoothDevice.LEGO_EV3);
}
if ((requiredResources & Brick.BLUETOOTH_PHIRO) != 0) {
connectBTDevice(BluetoothDevice.PHIRO);
}
if ((requiredResources & Brick.BLUETOOTH_SENSORS_ARDUINO) != 0) {
connectBTDevice(BluetoothDevice.ARDUINO);
}
if (DroneServiceWrapper.checkARDroneAvailability()) {
CatroidApplication.loadNativeLibs();
if (CatroidApplication.parrotLibrariesLoaded) {
droneInitializer = getDroneInitialiser();
droneInitializer.initialise();
}
}
if ((requiredResources & Brick.CAMERA_BACK) != 0) {
if (CameraManager.getInstance().hasBackCamera()) {
resourceInitialized();
} else {
resourceFailed(Brick.CAMERA_BACK);
}
}
if ((requiredResources & Brick.CAMERA_FRONT) != 0) {
if (CameraManager.getInstance().hasFrontCamera()) {
resourceInitialized();
} else {
resourceFailed(Brick.CAMERA_FRONT);
}
}
if ((requiredResources & Brick.VIDEO) != 0) {
if (CameraManager.getInstance().hasFrontCamera()
|| CameraManager.getInstance().hasBackCamera()) {
resourceInitialized();
} else {
resourceFailed(Brick.VIDEO);
}
}
if ((requiredResources & Brick.CAMERA_FLASH) != 0) {
flashInitialize();
}
if ((requiredResources & Brick.VIBRATOR) != 0) {
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
if (vibrator != null) {
VibratorUtil.setContext(this.getBaseContext());
VibratorUtil.activateVibratorThread();
resourceInitialized();
} else {
resourceFailed(Brick.VIBRATOR);
}
}
FaceDetectionHandler.resetFaceDedection();
if ((requiredResources & Brick.FACE_DETECTION) != 0) {
boolean success = FaceDetectionHandler.startFaceDetection();
if (success) {
resourceInitialized();
} else {
resourceFailed(Brick.FACE_DETECTION);
}
}
if ((requiredResources & Brick.NFC_ADAPTER) != 0) {
if ((requiredResources & Brick.FACE_DETECTION) != 0) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.nfc_facedetection_support)).setCancelable(false)
.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
nfcInitialize();
}
});
AlertDialog alert = builder.create();
alert.show();
} else {
nfcInitialize();
}
}
if ((requiredResources & Brick.COLLISION) != 0) {
GatherCollisionInformationTask task = new GatherCollisionInformationTask(this);
task.execute();
}
if ((requiredResources & Brick.NETWORK_CONNECTION) != 0) {
final Context finalBaseContext = this.getBaseContext();
if (!Utils.isNetworkAvailable(finalBaseContext)) {
List<Brick> networkBrickList = getBricksRequiringResource(Brick.NETWORK_CONNECTION);
networkBrickList = Utils.distinctListByClassOfObjects(networkBrickList);
NoNetworkDialog networkDialog = new NoNetworkDialog(this, networkBrickList);
networkDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
resourceFailed();
}
});
networkDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
if (Utils.isNetworkAvailable(finalBaseContext)) {
resourceInitialized();
} else {
resourceFailed();
}
}
});
networkDialog.show();
} else {
resourceInitialized();
}
}
if (requiredResourceCounter == Brick.NO_RESOURCES) {
startStage();
}
if ((requiredResources & Brick.SOCKET_RASPI) != 0) {
connectRaspberrySocket();
}
}
private void connectBTDevice(Class<? extends BluetoothDevice> service) {
BluetoothDeviceService btService = ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE);
if (btService.connectDevice(service, this, REQUEST_CONNECT_DEVICE)
== BluetoothDeviceService.ConnectDeviceResult.ALREADY_CONNECTED) {
resourceInitialized();
}
}
private void connectRaspberrySocket() {
String host = SettingsActivity.getRaspiHost(this.getBaseContext());
int port = SettingsActivity.getRaspiPort(this.getBaseContext());
if (RaspberryPiService.getInstance().connect(host, port)) {
resourceInitialized();
} else {
ToastUtil.showError(PreStageActivity.this, "Error: connecting to " + host + ":" + port + " failed");
resourceFailed();
}
}
public DroneInitializer getDroneInitialiser() {
if (droneInitializer == null) {
droneInitializer = new DroneInitializer(this);
}
return droneInitializer;
}
@Override
public void onResume() {
if (droneInitializer != null) {
droneInitializer.onPrestageActivityResume();
}
super.onResume();
if (requiredResourceCounter == 0 && failedResources.isEmpty()) {
Log.d(TAG, "onResume()");
finish();
}
}
@Override
protected void onPause() {
if (droneInitializer != null) {
droneInitializer.onPrestageActivityPause();
}
super.onPause();
}
@Override
protected void onDestroy() {
if (droneInitializer != null) {
droneInitializer.onPrestageActivityDestroy();
}
super.onDestroy();
}
//all resources that should be reinitialized with every stage start
public static void shutdownResources() {
if (textToSpeech != null) {
textToSpeech.stop();
textToSpeech.shutdown();
}
ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).pause();
if (FaceDetectionHandler.isFaceDetectionRunning()) {
FaceDetectionHandler.stopFaceDetection();
}
if (VibratorUtil.isActive()) {
VibratorUtil.pauseVibrator();
}
RaspberryPiService.getInstance().disconnect();
}
//all resources that should not have to be reinitialized every stage start
public static void shutdownPersistentResources() {
ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).disconnectDevices();
deleteSpeechFiles();
if (FlashUtil.isAvailable()) {
FlashUtil.destroy();
}
if (VibratorUtil.isActive()) {
VibratorUtil.destroy();
}
}
private static void deleteSpeechFiles() {
File pathToSpeechFiles = new File(Constants.TEXT_TO_SPEECH_TMP_PATH);
if (pathToSpeechFiles.isDirectory()) {
for (File file : pathToSpeechFiles.listFiles()) {
file.delete();
}
}
}
public void resourceFailed() {
setResult(RESULT_CANCELED, returnToActivityIntent);
finish();
}
public void showResourceFailedErrorDialog() {
String failedResourcesMessage = getString(R.string.prestage_resource_not_available_text);
Iterator resourceIter = failedResources.iterator();
while (resourceIter.hasNext()) {
switch ((int) resourceIter.next()) {
case Brick.SENSOR_ACCELERATION:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_acceleration_sensor_available);
break;
case Brick.SENSOR_INCLINATION:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_inclination_sensor_available);
break;
case Brick.SENSOR_COMPASS:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_compass_sensor_available);
break;
case Brick.SENSOR_GPS:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_gps_sensor_available);
break;
case Brick.TEXT_TO_SPEECH:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_text_to_speech_error);
break;
case Brick.CAMERA_BACK:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_back_camera_available);
break;
case Brick.CAMERA_FRONT:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_front_camera_available);
break;
case Brick.CAMERA_FLASH:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_flash_available);
break;
case Brick.VIBRATOR:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_vibrator_available);
break;
case Brick.FACE_DETECTION:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_no_camera_available);
break;
default:
failedResourcesMessage = failedResourcesMessage + getString(R.string
.prestage_default_resource_not_available);
break;
}
}
AlertDialog.Builder failedResourceAlertBuilder = new AlertDialog.Builder(this);
failedResourceAlertBuilder.setTitle(R.string.prestage_resource_not_available_title);
failedResourceAlertBuilder.setMessage(failedResourcesMessage).setCancelable(false)
.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
resourceFailed();
}
});
AlertDialog alert = failedResourceAlertBuilder.create();
alert.show();
}
public synchronized void resourceFailed(int failedResource) {
Log.d(TAG, "resourceFailed: " + failedResource);
failedResources.add(failedResource);
resourceInitialized();
}
public synchronized void resourceInitialized() {
requiredResourceCounter--;
if (requiredResourceCounter == 0) {
if (failedResources.isEmpty()) {
Log.d(TAG, "Start Stage");
startStage();
} else {
showResourceFailedErrorDialog();
}
}
}
public void startStage() {
for (Scene scene : ProjectManager.getInstance().getCurrentProject().getSceneList()) {
scene.firstStart = true;
scene.getDataContainer().resetAllDataObjects();
}
setResult(RESULT_OK, returnToActivityIntent);
finish();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "requestcode " + requestCode + " result code" + resultCode);
switch (requestCode) {
case REQUEST_CONNECT_DEVICE:
switch (resultCode) {
case Activity.RESULT_OK:
resourceInitialized();
break;
case Activity.RESULT_CANCELED:
resourceFailed();
break;
}
break;
case REQUEST_TEXT_TO_SPEECH:
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
textToSpeech = new TextToSpeech(getApplicationContext(), new OnInitListener() {
@Override
public void onInit(int status) {
onUtteranceCompletedListenerContainer = new OnUtteranceCompletedListenerContainer();
textToSpeech.setOnUtteranceCompletedListener(onUtteranceCompletedListenerContainer);
resourceInitialized();
if (status == TextToSpeech.ERROR) {
resourceFailed(Brick.TEXT_TO_SPEECH);
}
}
});
if (textToSpeech.isLanguageAvailable(Locale.getDefault()) == TextToSpeech.LANG_MISSING_DATA) {
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
resourceFailed();
}
} else {
AlertDialog.Builder builder = new CustomAlertDialogBuilder(this);
builder.setMessage(R.string.prestage_text_to_speech_engine_not_installed).setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
resourceFailed();
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
resourceFailed();
}
});
AlertDialog alert = builder.create();
alert.show();
}
break;
case REQUEST_GPS:
if (resultCode == RESULT_CANCELED && SensorHandler.gpsAvailable()) {
resourceInitialized();
} else {
resourceFailed(Brick.SENSOR_GPS);
}
break;
default:
resourceFailed();
break;
}
}
public static void textToSpeech(String text, File speechFile, OnUtteranceCompletedListener listener,
HashMap<String, String> speakParameter) {
if (text == null) {
text = "";
}
if (onUtteranceCompletedListenerContainer.addOnUtteranceCompletedListener(speechFile, listener,
speakParameter.get(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID))) {
int status = textToSpeech.synthesizeToFile(text, speakParameter, speechFile.getAbsolutePath());
if (status == TextToSpeech.ERROR) {
Log.e(TAG, "File synthesizing failed");
}
}
}
private void flashInitialize() {
if (CameraManager.getInstance().switchToCameraWithFlash()) {
FlashUtil.initializeFlash();
resourceInitialized();
} else {
resourceFailed(Brick.CAMERA_FLASH);
}
}
private void nfcInitialize() {
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
if (adapter != null && !adapter.isEnabled()) {
ToastUtil.showError(PreStageActivity.this, R.string.nfc_not_activated);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
Intent intent = new Intent(Settings.ACTION_NFC_SETTINGS);
startActivity(intent);
} else {
Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
startActivity(intent);
}
} else if (adapter == null) {
ToastUtil.showError(PreStageActivity.this, R.string.no_nfc_available);
// TODO: resourceFailed() & startActivityForResult(), if behaviour needed
}
resourceInitialized();
}
private static List<Brick> getBricksRequiringResource(int resource) {
List<Brick> brickList = new ArrayList<Brick>();
List<Scene> sceneList = ProjectManager.getInstance().getCurrentProject().getSceneList();
for (Scene scene : sceneList) {
for (Sprite sprite : scene.getSpriteList()) {
brickList.addAll(sprite.getBricksRequiringResource(resource));
}
}
return brickList;
}
@Override
public void onFinished() {
resourceInitialized();
}
}