/*
* 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.AlertDialog;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.speech.RecognizerIntent;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.EditText;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import org.catrobat.catroid.BuildConfig;
import org.catrobat.catroid.ProjectManager;
import org.catrobat.catroid.R;
import org.catrobat.catroid.camera.CameraManager;
import org.catrobat.catroid.common.CatroidService;
import org.catrobat.catroid.common.ScreenValues;
import org.catrobat.catroid.common.ServiceProvider;
import org.catrobat.catroid.content.BackgroundWaitHandler;
import org.catrobat.catroid.content.Sprite;
import org.catrobat.catroid.content.actions.AskAction;
import org.catrobat.catroid.content.bricks.Brick;
import org.catrobat.catroid.facedetection.FaceDetectionHandler;
import org.catrobat.catroid.formulaeditor.SensorHandler;
import org.catrobat.catroid.io.StageAudioFocus;
import org.catrobat.catroid.nfc.NfcHandler;
import org.catrobat.catroid.ui.MarketingActivity;
import org.catrobat.catroid.ui.dialogs.CustomAlertDialogBuilder;
import org.catrobat.catroid.ui.dialogs.StageDialog;
import org.catrobat.catroid.utils.FlashUtil;
import org.catrobat.catroid.utils.SnackbarUtil;
import org.catrobat.catroid.utils.UtilUi;
import org.catrobat.catroid.utils.VibratorUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class StageActivity extends AndroidApplication {
public static final String TAG = StageActivity.class.getSimpleName();
public static StageListener stageListener;
public static final int STAGE_ACTIVITY_FINISH = 7777;
public static final int ASK_MESSAGE = 0;
public static final int REGISTER_INTENT = 1;
private static final int PERFORM_INTENT = 2;
private StageAudioFocus stageAudioFocus;
private PendingIntent pendingIntent;
private NfcAdapter nfcAdapter;
private static BlockingDeque<NdefMessage> ndefMessageBlockingDeque = new LinkedBlockingDeque<NdefMessage>();
private StageDialog stageDialog;
private boolean resizePossible;
private boolean askDialogUnanswered = false;
private static int numberOfSpritesCloned;
public static Handler messageHandler;
public static Map<Integer, IntentListener> intentListeners = new HashMap<>();
public static Random randomGenerator = new Random();
AndroidApplicationConfiguration configuration = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate()");
numberOfSpritesCloned = 0;
setupAskHandler();
if (ProjectManager.getInstance().isCurrentProjectLandscapeMode()) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
stageListener = new StageListener();
stageDialog = new StageDialog(this, stageListener, R.style.stage_dialog);
calculateScreenSizes();
// need we this here?
configuration = new AndroidApplicationConfiguration();
configuration.r = configuration.g = configuration.b = configuration.a = 8;
initialize(stageListener, configuration);
if (graphics.getView() instanceof SurfaceView) {
SurfaceView glView = (SurfaceView) graphics.getView();
glView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
pendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
Log.d(TAG, "onCreate()");
if (nfcAdapter == null) {
Log.d(TAG, "could not get nfc adapter :(");
}
ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).initialise();
stageAudioFocus = new StageAudioFocus(this);
CameraManager.getInstance().setStageActivity(this);
BackgroundWaitHandler.reset();
SnackbarUtil.showHintSnackbar(this, R.string.hint_stage);
}
private void setupAskHandler() {
final StageActivity currentStage = this;
messageHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message message) {
List<Object> params = (ArrayList<Object>) message.obj;
switch (message.what) {
case ASK_MESSAGE:
showDialog((String) params.get(1), (AskAction) params.get(0));
break;
case REGISTER_INTENT:
currentStage.queueIntent((IntentListener) params.get(0));
break;
case PERFORM_INTENT:
currentStage.startQueuedIntent((Integer) params.get(0));
break;
default:
Log.e(TAG, "Unhandled message in messagehandler, case " + message.what);
break;
}
}
};
}
private void showDialog(String question, final AskAction askAction) {
pause();
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(new ContextThemeWrapper(this, android.R.style.Theme_Holo_Dialog));
final EditText edittext = new EditText(getContext());
alertBuilder.setView(edittext);
alertBuilder.setMessage(getContext().getString(R.string.brick_ask_dialog_hint));
alertBuilder.setTitle(question);
alertBuilder.setCancelable(false);
alertBuilder.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
return true;
}
return false;
}
});
alertBuilder.setPositiveButton(getContext().getString(R.string.brick_ask_dialog_submit), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String questionAnswer = edittext.getText().toString();
askAction.setAnswerText(questionAnswer);
askDialogUnanswered = false;
resume();
}
});
AlertDialog dialog = alertBuilder.create();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
askDialogUnanswered = true;
dialog.show();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "processIntent");
NfcHandler.processIntent(intent);
if (!ndefMessageBlockingDeque.isEmpty()) {
Tag currentTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
NfcHandler.writeTag(currentTag, ndefMessageBlockingDeque.poll());
}
}
@Override
public void onBackPressed() {
if (BuildConfig.FEATURE_APK_GENERATOR_ENABLED) {
PreStageActivity.shutdownPersistentResources();
Intent marketingIntent = new Intent(StageActivity.this, MarketingActivity.class);
startActivity(marketingIntent);
finish();
} else {
pause();
stageDialog.show();
}
}
public void manageLoadAndFinish() {
stageListener.pause();
stageListener.finish();
PreStageActivity.shutdownResources();
}
@Override
public void onPause() {
if (nfcAdapter != null) {
try {
nfcAdapter.disableForegroundDispatch(this);
} catch (IllegalStateException illegalStateException) {
Log.e(TAG, "Disabling NFC foreground dispatching went wrong!", illegalStateException);
}
}
SensorHandler.stopSensorListeners();
stageAudioFocus.releaseAudioFocus();
FlashUtil.pauseFlash();
FaceDetectionHandler.pauseFaceDetection();
CameraManager.getInstance().pausePreview();
CameraManager.getInstance().releaseCamera();
VibratorUtil.pauseVibrator();
super.onPause();
ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).pause();
}
@Override
public void onResume() {
resumeResources();
super.onResume();
}
public void pause() {
if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(this);
}
SensorHandler.stopSensorListeners();
stageListener.menuPause();
FlashUtil.pauseFlash();
VibratorUtil.pauseVibrator();
FaceDetectionHandler.pauseFaceDetection();
CameraManager.getInstance().pausePreviewAsync();
ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).pause();
}
public void resume() {
if (askDialogUnanswered) {
return;
}
stageListener.menuResume();
resumeResources();
}
public void resumeResources() {
int requiredResources = ProjectManager.getInstance().getCurrentProject().getRequiredResources();
List<Sprite> spriteList = ProjectManager.getInstance().getSceneToPlay().getSpriteList();
SensorHandler.startSensorListener(this);
for (Sprite sprite : spriteList) {
if (sprite.getPlaySoundBricks().size() > 0) {
stageAudioFocus.requestAudioFocus();
break;
}
}
if ((requiredResources & Brick.CAMERA_FLASH) != 0) {
FlashUtil.resumeFlash();
}
if ((requiredResources & Brick.VIBRATOR) != 0) {
VibratorUtil.resumeVibrator();
}
if ((requiredResources & Brick.FACE_DETECTION) != 0) {
FaceDetectionHandler.resumeFaceDetection();
}
if ((requiredResources & Brick.BLUETOOTH_LEGO_NXT) != 0
|| (requiredResources & Brick.BLUETOOTH_PHIRO) != 0
|| (requiredResources & Brick.BLUETOOTH_SENSORS_ARDUINO) != 0) {
ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).start();
}
if ((requiredResources & Brick.CAMERA_BACK) != 0
|| (requiredResources & Brick.CAMERA_FRONT) != 0
|| (requiredResources & Brick.VIDEO) != 0) {
CameraManager.getInstance().resumePreviewAsync();
}
if ((requiredResources & Brick.TEXT_TO_SPEECH) != 0) {
stageAudioFocus.requestAudioFocus();
}
if ((requiredResources & Brick.NFC_ADAPTER) != 0
&& nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
}
public boolean getResizePossible() {
return resizePossible;
}
private void calculateScreenSizes() {
UtilUi.updateScreenWidthAndHeight(getContext());
int virtualScreenWidth = ProjectManager.getInstance().getCurrentProject().getXmlHeader().virtualScreenWidth;
int virtualScreenHeight = ProjectManager.getInstance().getCurrentProject().getXmlHeader().virtualScreenHeight;
if (virtualScreenHeight > virtualScreenWidth) {
iflandscapeModeSwitchWidthAndHeight();
} else {
ifPortraitSwitchWidthAndHeight();
}
float aspectRatio = (float) virtualScreenWidth / (float) virtualScreenHeight;
float screenAspectRatio = ScreenValues.getAspectRatio();
if ((virtualScreenWidth == ScreenValues.SCREEN_WIDTH && virtualScreenHeight == ScreenValues.SCREEN_HEIGHT)
|| Float.compare(screenAspectRatio, aspectRatio) == 0) {
resizePossible = false;
stageListener.maximizeViewPortWidth = ScreenValues.SCREEN_WIDTH;
stageListener.maximizeViewPortHeight = ScreenValues.SCREEN_HEIGHT;
return;
}
resizePossible = true;
float scale = 1f;
float ratioHeight = (float) ScreenValues.SCREEN_HEIGHT / (float) virtualScreenHeight;
float ratioWidth = (float) ScreenValues.SCREEN_WIDTH / (float) virtualScreenWidth;
if (aspectRatio < screenAspectRatio) {
scale = ratioHeight / ratioWidth;
stageListener.maximizeViewPortWidth = (int) (ScreenValues.SCREEN_WIDTH * scale);
stageListener.maximizeViewPortX = (int) ((ScreenValues.SCREEN_WIDTH - stageListener.maximizeViewPortWidth) / 2f);
stageListener.maximizeViewPortHeight = ScreenValues.SCREEN_HEIGHT;
} else if (aspectRatio > screenAspectRatio) {
scale = ratioWidth / ratioHeight;
stageListener.maximizeViewPortHeight = (int) (ScreenValues.SCREEN_HEIGHT * scale);
stageListener.maximizeViewPortY = (int) ((ScreenValues.SCREEN_HEIGHT - stageListener.maximizeViewPortHeight) / 2f);
stageListener.maximizeViewPortWidth = ScreenValues.SCREEN_WIDTH;
}
}
private void iflandscapeModeSwitchWidthAndHeight() {
if (ScreenValues.SCREEN_WIDTH > ScreenValues.SCREEN_HEIGHT) {
int tmp = ScreenValues.SCREEN_HEIGHT;
ScreenValues.SCREEN_HEIGHT = ScreenValues.SCREEN_WIDTH;
ScreenValues.SCREEN_WIDTH = tmp;
}
}
private void ifPortraitSwitchWidthAndHeight() {
if (ScreenValues.SCREEN_WIDTH < ScreenValues.SCREEN_HEIGHT) {
int tmp = ScreenValues.SCREEN_HEIGHT;
ScreenValues.SCREEN_HEIGHT = ScreenValues.SCREEN_WIDTH;
ScreenValues.SCREEN_WIDTH = tmp;
}
}
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy()");
ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).destroy();
FlashUtil.destroy();
VibratorUtil.destroy();
FaceDetectionHandler.stopFaceDetection();
CameraManager.getInstance().stopPreviewAsync();
CameraManager.getInstance().releaseCamera();
CameraManager.getInstance().setToDefaultCamera();
ProjectManager.getInstance().setSceneToPlay(ProjectManager.getInstance().getCurrentScene());
super.onDestroy();
}
@Override
public ApplicationListener getApplicationListener() {
return stageListener;
}
@Override
public void log(String tag, String message, Throwable exception) {
Log.d(tag, message, exception);
}
@Override
public int getLogLevel() {
return 0;
}
//for running Asynchronous Tasks from the stage
public void post(Runnable r) {
handler.post(r);
}
public void destroy() {
stageListener.finish();
manageLoadAndFinish();
final AlertDialog.Builder builder = new CustomAlertDialogBuilder(this);
builder.setMessage(R.string.error_flash_front_camera).setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
onDestroy();
exit();
}
});
this.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
AlertDialog dialog = builder.create();
dialog.show();
} catch (Exception e) {
Log.e(TAG, "Error while showing dialog. " + e.getMessage());
}
}
});
}
public static int getAndIncrementNumberOfClonedSprites() {
return ++numberOfSpritesCloned;
}
public static void addNfcTagMessageToDeque(NdefMessage message) {
ndefMessageBlockingDeque.addLast(message);
}
public synchronized void queueIntent(IntentListener asker) {
if (StageActivity.messageHandler == null) {
return;
}
int newIdentId;
do {
newIdentId = StageActivity.randomGenerator.nextInt(Integer.MAX_VALUE);
} while (intentListeners.containsKey(newIdentId));
intentListeners.put(newIdentId, asker);
ArrayList<Object> params = new ArrayList<>();
params.add(newIdentId);
Message message = StageActivity.messageHandler.obtainMessage(StageActivity.PERFORM_INTENT, params);
message.sendToTarget();
}
private void startQueuedIntent(int intentKey) {
if (!intentListeners.containsKey(intentKey)) {
return;
}
Intent i = intentListeners.get(intentKey).getTargetIntent();
i.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getClass().getPackage().getName());
this.startActivityForResult(i, intentKey);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//Register your intent with "queueIntent"
if (!intentListeners.containsKey(requestCode)) {
Log.e(TAG, "Unknown intent result recieved!");
} else {
IntentListener asker = intentListeners.get(requestCode);
asker.onIntentResult(resultCode, data);
intentListeners.remove(requestCode);
}
}
public interface IntentListener {
Intent getTargetIntent();
void onIntentResult(int resultCode, Intent data); //don't do heavy processing here
}
}