/* * Copyright 2015 Daniel Dittmar * * 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. * */ package dan.dit.whatsthat.riddle; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import com.plattysoft.leonids.Particle; import com.plattysoft.leonids.ParticleField; import com.plattysoft.leonids.ParticleFieldController; import com.plattysoft.leonids.ParticleSystem; import java.util.ArrayList; import java.util.List; import dan.dit.whatsthat.R; import dan.dit.whatsthat.riddle.control.RiddleController; import dan.dit.whatsthat.riddle.types.PracticalRiddleType; import dan.dit.whatsthat.system.NoPanicDialog; import dan.dit.whatsthat.testsubject.TestSubjectToast; /** * Created by daniel on 31.03.15. */ public class RiddleView extends SurfaceView implements SensorEventListener, ParticleField { public static final int BACKGROUND_COLOR_RESOURCE_ID = R.color.main_background; private RiddleController mRiddleCtr; private SensorManager mSensorManager; private Sensor mMagnetometer; private Sensor mAccelerometer; private boolean mIsResumed; private int mBackgroundColor; private volatile List<List<Particle>> mParticles; private ParticleController mController; public RiddleView(Context context, AttributeSet attrs) { super(context, attrs); mController = new ParticleController(); mParticles = new ArrayList<>(); setFocusable(true); setVisibility(View.INVISIBLE); mBackgroundColor = getResources().getColor(BACKGROUND_COLOR_RESOURCE_ID); //setZOrderOnTop(true); // necessary if there is no background color drawn to clear // but simply a clearing paint, because else the background // is black getHolder().setFormat(PixelFormat.RGBA_8888); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { if (hasController()) { mRiddleCtr.onMotionEvent(event); } return true; } }); } public void onPause() { if (!mIsResumed) { return; // already paused } Log.d("Riddle", "Pausing riddle view, has controller: " + hasController()); mIsResumed = false; if (mSensorManager != null) { mSensorManager.unregisterListener(this); } if (hasController()) { mRiddleCtr.stopPeriodicEvent(); } } public void onResume() { if (mIsResumed) { return; // already resumed } mIsResumed = true; if (hasController() && mSensorManager != null) { mSensorManager.unregisterListener(this); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_NORMAL); } if (hasController()) { mRiddleCtr.resumePeriodicEventIfRequired(); draw(); } } public synchronized void removeController() { ensureHasController(); RiddleController ctr = mRiddleCtr; mRiddleCtr = null; // so any input and drawing will no longer be done if (mSensorManager != null) { mSensorManager.unregisterListener(this); mSensorManager = null; mAccelerometer = null; mMagnetometer = null; mAccelerometerValues = null; mGeomagneticValues = null; } ctr.onCloseRiddle(getContext()); } public long performDrawRiddle() { long startTime = System.nanoTime(); SurfaceHolder holder = getHolder(); if (holder != null && holder.getSurface() != null && holder.getSurface().isValid()) { Canvas canvas = holder.lockCanvas(); if (canvas != null) { // clear previously drawn artifacts (view is triple buffered), very important if there is any pixel with alpha drawn by the riddle canvas.drawColor(mBackgroundColor); if (hasController()) { mRiddleCtr.draw(canvas); } drawParticles(canvas); holder.unlockCanvasAndPost(canvas); } } return (System.nanoTime() - startTime) / 1000000; } private void drawParticles(Canvas canvas) { for (final List<Particle> particles : mParticles) { // Draw all the particles if (particles != null && !particles.isEmpty()) { synchronized (particles) { for (int i = 0; i < particles.size(); i++) { particles.get(i).draw(canvas); } } } } } public void draw() { if (mRiddleCtr != null && !mRiddleCtr.hasRunningPeriodicThread()) { performDrawRiddle(); } } public synchronized void setController(@NonNull RiddleController controller) { if (mRiddleCtr != null) { throw new IllegalStateException("Already initialized a controller!"); } mRiddleCtr = controller; mRiddleCtr.onRiddleVisible((ViewGroup) getParent()); mController.clear(); if (mRiddleCtr.requiresOrientationSensor()) { mSensorManager = (SensorManager) getContext().getSystemService(Activity.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); if (mAccelerometer == null || mMagnetometer == null) { Log.d("Riddle", "Missing sensor(s): " + mAccelerometer + " / " + mMagnetometer); mSensorManager = null; mAccelerometer = null; mMagnetometer = null; mRiddleCtr.enableNoOrientationSensorAlternative(); } else { mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_NORMAL); mAccelerometerValues = new float[3]; mGeomagneticValues = new float[3]; } } if (mIsResumed) { mRiddleCtr.resumePeriodicEventIfRequired(); } else { onPause(); } setVisibility(View.VISIBLE); draw(); } public synchronized boolean hasController() { return mRiddleCtr != null; } @SuppressWarnings("SuspiciousNameCombination") @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight(); int heightWithoutPadding = height - getPaddingTop() - getPaddingBottom(); // pick the smaller of each to make it square if (widthWithoutPadding > heightWithoutPadding) { widthWithoutPadding = heightWithoutPadding; } else { heightWithoutPadding = widthWithoutPadding; } // add padding and ensure we are >= suggested dimensions int widthWithPadding = widthWithoutPadding + getPaddingLeft() + getPaddingRight(); int heightWithPadding = heightWithoutPadding + getPaddingBottom() + getPaddingTop(); if (widthWithPadding < getSuggestedMinimumWidth()) { widthWithPadding = getSuggestedMinimumWidth(); } if (heightWithPadding < getSuggestedMinimumHeight()) { heightWithPadding = getSuggestedMinimumHeight(); } // return the dimensions we want setMeasuredDimension(widthWithPadding, heightWithPadding); } public long getRiddleId() { return mRiddleCtr.getRiddleId(); } private float[] mAccelerometerValues; private float[] mGeomagneticValues; private float[] mR = new float[9]; private float[] mI = new float[9]; private float[] mOrientation = new float[3]; private float[] mROut = new float[mR.length]; public void onSensorChanged(SensorEvent event) { if (mAccelerometerValues != null && mGeomagneticValues != null) { switch(event.sensor.getType()){ case Sensor.TYPE_ACCELEROMETER: System.arraycopy(event.values, 0, mAccelerometerValues, 0, 3); break; case Sensor.TYPE_MAGNETIC_FIELD: System.arraycopy(event.values, 0, mGeomagneticValues, 0, 3); break; } boolean success = SensorManager.getRotationMatrix(mR, mI, mAccelerometerValues, mGeomagneticValues); if (success) { /*// transform the normal vector g=(0 0 1) into new coordinate space by calculating R^-1 * g = R'*g float[] gravity = new float[3]; for (int i = 0; i < gravity.length; i++) { gravity[i] = R[6 + i]; } mRiddleCtr.onOrientationEvent(gravity); Log.d("Riddle", "Gravity normal vector: " + Arrays.toString(gravity));*/ // to get azimuth, pitch and roll: SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_X, SensorManager.AXIS_Y, mROut); SensorManager.getOrientation(mROut, mOrientation); mRiddleCtr.onOrientationEvent(mOrientation[0], mOrientation[1], mOrientation[2]); } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } public void supplyNoPanicParams(Bundle args) { if (!hasController()) { return; } args.putString(NoPanicDialog.KEY_TYPE, mRiddleCtr.getRiddleType().getFullName()); args.putString(NoPanicDialog.KEY_IMAGE, mRiddleCtr.getImageHash()); } public PracticalRiddleType getRiddleType() { ensureHasController(); return mRiddleCtr.getRiddleType(); } @Override public ParticleFieldController getParticleController() { return mController; } @Override public void setParticles(List<Particle> particles) { mParticles.add(particles); } private boolean removeParticles(List<Particle> particles) { for (int i = 0; i < mParticles.size(); i++) { if (mParticles.get(i) == particles) { mParticles.remove(i); return true; } } return false; } public Riddle getRiddle() { ensureHasController(); return mRiddleCtr.getRiddle(); } public void forbidRiddleBonusScore() { ensureHasController(); mRiddleCtr.forbidRiddleBonusScore(); } private void ensureHasController() { if (!hasController()) { throw new IllegalStateException("No controller initialized."); } } public boolean isPaused() { return !mIsResumed; } private class ParticleController implements ParticleFieldController { private void clear() { mParticles.clear(); } @Override public int getPositionInParentX() { return 0; } @Override public int getPositionInParentY() { return 0; } @Override public void prepareEmitting(List<Particle> particles) { if (!removeParticles(particles)) { setParticles(particles); RiddleController ctr = mRiddleCtr; if (ctr != null) { ctr.onParticleSystemCountChanged(); } } } @Override public void onUpdate() { } @Override public void onCleanup(ParticleSystem toClean) { if (removeParticles(toClean.getActiveParticles())) { RiddleController ctr = mRiddleCtr; if (ctr != null) { ctr.onParticleSystemCountChanged(); } } } } public int getActiveParticleSystemsCount() { return mParticles.size(); } public interface PartyCallback { void doParty(int partyParam); void giveCandy(TestSubjectToast toast); void showMoneyEarned(int moneyEarned); } public void checkParty(@NonNull Resources res, @NonNull PartyCallback callback) { ensureHasController(); mRiddleCtr.checkParty(res, callback); } }