/*
* Copyright (C) 2013 Fairphone Project
*
* 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 org.fairphone.peaceofmind;
import com.flurry.android.FlurryAgent;
import org.fairphone.fairphonepeaceofmindapp.R;
import org.fairphone.peaceofmind.data.PeaceOfMindStats;
import org.fairphone.peaceofmind.ui.VerticalScrollListener;
import org.fairphone.peaceofmind.ui.VerticalSeekBar;
import org.fairphone.peaceofmind.utils.FlurryHelper;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.TextView;
import android.widget.VideoView;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class PeaceOfMindActivity extends Activity implements VerticalScrollListener, PeaceOfMindApplicationBroadcastReceiver.Listener, OnPreparedListener, OnCompletionListener
{
public static String START_PEACE_OF_MIND = "START_PEACE_OF_MIND";
public static String END_PEACE_OF_MIND = "END_PEACE_OF_MIND";
public static String UPDATE_PEACE_OF_MIND = "UPDATE_PEACE_OF_MIND";
public static final String TIMER_TICK = "TIMER_TICK";
public static int count = 0;
private static final float INITIAL_PERCENTAGE = 0.1f;
public static final int MINUTE = 60 * 1000;
public static final int HOUR = 60 * MINUTE;
protected static final String TAG = PeaceOfMindActivity.class.getSimpleName();
public static final String BROADCAST_TARGET_PEACE_OF_MIND = "BROADCAST_TARGET_PEACE_OF_MIND";
private TextView mTotalTimeText;
private LinearLayout mCurrentTimeGroup;
private TextView mCurrentTimeText;
private LinearLayout mCurrentToTimeGroup;
private TextView mCurrentToTimeText;
private TextView mCurrentToText;
private TextView mCurrentTimeAtText;
private TextView mCurrentTimePEACEText;
private LinearLayout mCurrentTimeInPeaceText;
private VerticalSeekBar mVerticalSeekBar;
private View mProgressView;
private Button mHelpButton;
private View mSeekbarBackgroundOff;
private View mSeekbarBackgroundOn;
private FrameLayout mHelpHolder;
private LinearLayout mHelpLayout;
private Button mCloseButton;
private VideoView mVideo;
private PeaceOfMindApplicationBroadcastReceiver mBroadCastReceiver;
private SharedPreferences mSharedPreferences;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupLayout();
registerForPeaceOfMindBroadCasts();
setupBroadCastReceiverAlarm();
}
private void loadAvailableData()
{
PeaceOfMindStats currentStats = PeaceOfMindStats.getStatsFromSharedPreferences(mSharedPreferences);
mVerticalSeekBar.setThumb(getResources().getDrawable(currentStats.mIsOnPeaceOfMind ? R.drawable.seekbar_thumb_on : R.drawable.seekbar_thumb_off));
mVerticalSeekBar.setThumbOffset(0);
if (currentStats.mIsOnPeaceOfMind)
{
float targetTimePercent = (float) currentStats.mCurrentRun.mTargetTime / (float) PeaceOfMindStats.MAX_TIME;
mVerticalSeekBar.setInvertedProgress((int) (targetTimePercent * mVerticalSeekBar.getHeight()));
updateTextForNewTime(currentStats.mCurrentRun.mPastTime, currentStats.mCurrentRun.mTargetTime);
updateTimeTextLabel(targetTimePercent * 100);
updateScreenTexts();
}
else
{
mTotalTimeText.setText(generateStringTimeFromMillis(0, true));
mCurrentTimeText.setText(generateStringTimeFromMillis(0, true));
}
updateBackground(currentStats.mIsOnPeaceOfMind);
mVideo.setBackgroundResource(currentStats.mIsOnPeaceOfMind ? R.drawable.background_on_repeat : R.drawable.background_off_repeat);
}
private void updateScreenTexts()
{
PeaceOfMindStats currentStats = PeaceOfMindStats.getStatsFromSharedPreferences(mSharedPreferences);
int blue = getResources().getColor(R.color.blue);
int grey = getResources().getColor(R.color.blue_grey);
if (currentStats.mIsOnPeaceOfMind)
{
// current time is blue
mCurrentTimeText.setTextColor(blue);
mCurrentTimeAtText.setTextColor(blue);
mCurrentTimePEACEText.setTextColor(blue);
mCurrentTimeText.setAlpha(1.0f);
mCurrentTimeAtText.setAlpha(1.0f);
mCurrentTimePEACEText.setAlpha(1.0f);
mCurrentToTimeGroup.setVisibility(View.VISIBLE);
mSeekbarBackgroundOff.setVisibility(View.GONE);
mSeekbarBackgroundOn.setVisibility(View.VISIBLE);
}
else
{
// show the current time and text at grey
mCurrentTimeText.setTextColor(grey);
mCurrentTimeAtText.setTextColor(grey);
mCurrentTimePEACEText.setTextColor(grey);
mCurrentTimeText.setAlpha(0.5f);
mCurrentTimeAtText.setAlpha(0.5f);
mCurrentTimePEACEText.setAlpha(0.5f);
mCurrentToTimeGroup.setVisibility(View.INVISIBLE);
mSeekbarBackgroundOff.setVisibility(View.VISIBLE);
mSeekbarBackgroundOn.setVisibility(View.GONE);
}
if (mTotalTimeText.getVisibility() == View.VISIBLE)
{
//hide the current time group when the target time approaches
//TODO: Fix the ugly magical numbers
float position = mCurrentTimeGroup.getY() - mTotalTimeText.getY();
float alpha = (position < 500) ? (10.0f * (position-50) / 100.0f) : 1.0f;
mCurrentTimeGroup.setAlpha(alpha);
mCurrentTimeInPeaceText.setAlpha(alpha);
}
else
{
if(mCurrentTimeGroup.getAlpha() != 1.0f ){
if(!currentStats.mIsOnPeaceOfMind){
Animation fadeIn = AnimationUtils.loadAnimation(this, R.anim.target_time_fade_in_fast);
mCurrentTimeGroup.startAnimation(fadeIn);
mCurrentTimeInPeaceText.startAnimation(fadeIn);
}
mCurrentTimeGroup.setAlpha(1.0f);
mCurrentTimeInPeaceText.setAlpha(1.0f);
}
}
}
private void updateBackground(boolean on)
{
View backgroundOverlay = findViewById(R.id.backgroundOverlay);
int backgroundDrawableId = on ? R.drawable.background_on_repeat : R.drawable.background_off_repeat;
// setup the background
backgroundOverlay.setBackgroundResource(backgroundDrawableId);
}
@Override
protected void onResume()
{
super.onResume();
mVideo.setVisibility(View.INVISIBLE);
mVideo.stopPlayback();
// load data from the shared preferences
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
updateScreenTexts();
}
@Override
protected void onPause()
{
super.onPause();
if (isFinishing())
{
unRegisterForPeaceOfMindBroadCasts();
}
}
@Override
protected void onDestroy()
{
super.onDestroy();
}
private void setupLayout()
{
mTotalTimeText = (TextView) findViewById(R.id.timeTextTotal);
mCurrentTimeGroup = (LinearLayout) findViewById(R.id.timeTextCurrentGroup);
mCurrentTimeText = (TextView) findViewById(R.id.timeTextCurrent);
mCurrentToTimeGroup = (LinearLayout) findViewById(R.id.toTimeGroup);
mCurrentToTimeText = (TextView) findViewById(R.id.toTimeText);
mCurrentToText = (TextView) findViewById(R.id.toText);
mCurrentTimeInPeaceText = (LinearLayout) findViewById(R.id.inPeaceTextCurrent);
mCurrentTimeAtText = (TextView) findViewById(R.id.currentAtText);
mCurrentTimePEACEText = (TextView) findViewById(R.id.currentPeaceText);
mVerticalSeekBar = (VerticalSeekBar) findViewById(R.id.verticalSeekBar);
mProgressView = (View) findViewById(R.id.progressView);
mHelpButton = (Button) findViewById(R.id.helpButton);
mSeekbarBackgroundOff = findViewById(R.id.seekbar_background_off);
mSeekbarBackgroundOn = findViewById(R.id.seekbar_background_on);
if (mVerticalSeekBar != null)
{
mVerticalSeekBar.setPeaceListener(this);
}
mHelpHolder = (FrameLayout) findViewById(R.id.helpHolder);
mHelpLayout = (LinearLayout) findViewById(R.id.helpLayout);
mCloseButton = (Button) findViewById(R.id.closeButton);
mHelpButton.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
showHelp();
}
});
mCloseButton.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
hideHelp();
}
});
mVideo = (VideoView) findViewById(R.id.pomVideo);
mVideo.setVisibility(View.INVISIBLE);
Uri uri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.fp_start_pom_video);
mVideo.setMediaController(null);
mVideo.requestFocus();
mVideo.setVideoURI(uri);
}
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
loadAvailableData();
}
public void showHelp()
{
// disable the seekbar and help button when the help is showed
mVerticalSeekBar.setEnabled(false);
mHelpButton.setEnabled(false);
mHelpHolder.setVisibility(View.VISIBLE);
ObjectAnimator showIn = ObjectAnimator.ofFloat(mHelpHolder, "alpha", 0, 1);
showIn.setDuration(400);
showIn.start();
showIn.addListener(new AnimatorListener()
{
@Override
public void onAnimationStart(Animator animation)
{
}
@Override
public void onAnimationRepeat(Animator animation)
{
}
@Override
public void onAnimationEnd(Animator animation)
{
mHelpLayout.setVisibility(View.VISIBLE);
Interpolator decelerator = new DecelerateInterpolator();
ObjectAnimator translateIn = ObjectAnimator.ofFloat(mHelpLayout, "translationY", 900f, 0f);
translateIn.setInterpolator(decelerator);
translateIn.setDuration(400);
translateIn.start();
}
@Override
public void onAnimationCancel(Animator animation)
{
}
});
}
public void hideHelp()
{
ObjectAnimator showIn = ObjectAnimator.ofFloat(mHelpHolder, "alpha", 1, 0);
showIn.setDuration(400);
showIn.start();
showIn.addListener(new AnimatorListener()
{
@Override
public void onAnimationStart(Animator animation)
{
}
@Override
public void onAnimationRepeat(Animator animation)
{
}
@Override
public void onAnimationEnd(Animator animation)
{
mHelpButton.setEnabled(true);
mVerticalSeekBar.setEnabled(true);
mHelpHolder.setVisibility(View.GONE);
mHelpLayout.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animation)
{
}
});
}
@Override
public void onBackPressed() {
if (mHelpLayout.getVisibility() == View.VISIBLE) {
hideHelp();
} else {
// allows standard use of back button for page 1
super.onBackPressed();
}
}
private void registerForPeaceOfMindBroadCasts()
{
IntentFilter filter = new IntentFilter();
filter.addAction(PeaceOfMindApplicationBroadcastReceiver.PEACE_OF_MIND_STARTED);
filter.addAction(PeaceOfMindApplicationBroadcastReceiver.PEACE_OF_MIND_UPDATED);
filter.addAction(PeaceOfMindApplicationBroadcastReceiver.PEACE_OF_MIND_ENDED);
filter.addAction(PeaceOfMindApplicationBroadcastReceiver.PEACE_OF_MIND_TICK);
mBroadCastReceiver = new PeaceOfMindApplicationBroadcastReceiver(this);
registerReceiver(mBroadCastReceiver, filter);
}
private void unRegisterForPeaceOfMindBroadCasts()
{
unregisterReceiver(mBroadCastReceiver);
}
private void setupBroadCastReceiverAlarm()
{
Log.d(TAG, "Setting the alarm tick");
Intent alarmIntent = new Intent(this, PeaceOfMindBroadCastReceiver.class);
alarmIntent.setAction(PeaceOfMindActivity.TIMER_TICK);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) this.getSystemService(this.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), MINUTE, pendingIntent);
}
@Override
public void updateBarScroll(float progress)
{
// TODO: Put the 612 seekbar dimension in resources in dp if possible
int pos = (int) (612 - (612 / 100 * progress) + (progress / 2) - 53);
updateTimeTextLabel(progress);
if(mTotalTimeText.getVisibility() == View.INVISIBLE){
mTotalTimeText.setVisibility(View.VISIBLE);
Animation fadeIn = AnimationUtils.loadAnimation(this, R.anim.target_time_fade_in_fast);
mTotalTimeText.startAnimation(fadeIn);
}
mTotalTimeText.setY(pos);
updateScreenTexts();
}
@Override
public synchronized void scrollEnded(float percentage)
{
if(mTotalTimeText.getVisibility() == View.VISIBLE){
Animation fadeOut = AnimationUtils.loadAnimation(this, R.anim.fade_out_fast);
mTotalTimeText.startAnimation(fadeOut);
fadeOut.setAnimationListener(new AnimationListener()
{
@Override
public void onAnimationStart(Animation animation)
{
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
@Override
public void onAnimationEnd(Animation animation)
{
mTotalTimeText.setVisibility(View.INVISIBLE);
updateScreenTexts();
}
});
}
long targetTime = roundToInterval((long) (percentage * PeaceOfMindStats.MAX_TIME));
Intent intent = new Intent(getApplicationContext(), PeaceOfMindBroadCastReceiver.class);
intent.setAction(PeaceOfMindActivity.UPDATE_PEACE_OF_MIND);
intent.putExtra(PeaceOfMindActivity.BROADCAST_TARGET_PEACE_OF_MIND, targetTime);
FlurryAgent.logEvent(FlurryHelper.PEACE_OF_MIND_SET_TIME, FlurryHelper.getInstance().setFlurryParams("Target Time", "" + targetTime, true));
sendBroadcast(intent);
}
private long roundToInterval(long time)
{
int hours = (int) (time / HOUR);
int minutes = (int) ((time - hours * HOUR) / MINUTE);
int index = minutes % 10;
long newTime = 0;
switch (index)
{
case 1:
case 6:
newTime -= MINUTE;
break;
case 2:
case 7:
newTime -= 2 * MINUTE;
break;
case 3:
case 8:
newTime += 2 * MINUTE;
break;
case 4:
case 9:
newTime += MINUTE;
break;
}
Log.d(TAG, "Index: " + index + " - " + newTime);
return time + newTime;
}
private void updateTextForNewTime(long timePast, long targetTime)
{
long maxTime = PeaceOfMindStats.MAX_TIME;
float timePercentage = 0;
long timeUntilTarget = targetTime - timePast;
mCurrentTimeText.setText(generateStringTimeFromMillis(timeUntilTarget, timeUntilTarget <= 0));
int finalY = getCurrentProgressY(timePast, targetTime, maxTime, timePercentage);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, finalY);
mProgressView.setLayoutParams(params);
float pos = mVerticalSeekBar.getHeight() - finalY - 12;
mCurrentTimeGroup.setY(pos);
mCurrentTimeInPeaceText.setY(pos);
}
private int getCurrentProgressY(long timePast, long targetTime, long maxTime, float timePercentage)
{
if (targetTime > 0)
{
timePercentage = (((float) timePast / (float) (maxTime)));
}
System.out.println("Updating time to " + timePercentage + " - " + timePast + " target time " + targetTime);
int finalY = (int) (0.8f * mVerticalSeekBar.getHeight() * timePercentage + (mVerticalSeekBar.getHeight() * INITIAL_PERCENTAGE));
return finalY;
}
private String generateStringTimeFromMillis(long timePast, boolean reset)
{
int hours = 0;
int minutes = 0;
if(!reset){
hours = (int) (timePast / HOUR);
int timeInMinutes = (int) (timePast - hours * HOUR);
if(hours == 0){
minutes = timeInMinutes - MINUTE > 0 ? timeInMinutes / MINUTE : 1;
}else{
minutes = timeInMinutes / MINUTE;
}
}
String timeStr = String.format("%d%s%02d", hours, getResources().getString(R.string.hour_separator), minutes);
if(hours == 0){
mCurrentToText.setText(getResources().getString(R.string.to_m));
}else{
mCurrentToText.setText(getResources().getString(R.string.to_h));
}
return timeStr;
}
private void updateTimeTextLabel(float progress)
{
long targetTime = roundToInterval((long) (PeaceOfMindStats.MAX_TIME * progress / 100.0f));
mTotalTimeText.setText(generateStringTimeFromMillis(targetTime, targetTime == 0));
mCurrentToTimeText.setText(generateStringTimeFromMillis(targetTime, targetTime == 0));
}
@Override
public void peaceOfMindTick(long pastTime, long targetTime)
{
updateTextForNewTime(pastTime, targetTime);
updateScreenTexts();
}
static Semaphore mSemaphore = new Semaphore(1);
@Override
public synchronized void peaceOfMindStarted(long targetTime)
{
FlurryAgent.logEvent(FlurryHelper.PEACE_OF_MIND_STARTED, true);
try {
mSemaphore.tryAcquire(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
Animation fadeOut = AnimationUtils.loadAnimation(this, R.anim.fade_out_fast);
mSeekbarBackgroundOff.startAnimation(fadeOut);
Animation fadeIn = AnimationUtils.loadAnimation(this, R.anim.fade_in_fast);
mSeekbarBackgroundOn.startAnimation(fadeIn);
fadeIn.setAnimationListener(new AnimationListener()
{
@Override
public void onAnimationStart(Animation animation)
{
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
@Override
public void onAnimationEnd(Animation animation)
{
mSeekbarBackgroundOff.setVisibility(View.GONE);
mSeekbarBackgroundOn.setVisibility(View.VISIBLE);
mSemaphore.release();
}
});
mVerticalSeekBar.setThumb(getResources().getDrawable(R.drawable.seekbar_thumb_on));
mVerticalSeekBar.setThumbOffset(0);
// fix thumb position
float targetTimePercent = (float) targetTime / (float) PeaceOfMindStats.MAX_TIME;
updateTextForNewTime(0, targetTime);
mVerticalSeekBar.setInvertedProgress((int) (targetTimePercent * mVerticalSeekBar.getHeight()));
startPeaceOfMindVideo();
}
private void startPeaceOfMindVideo()
{
mVideo.setBackgroundResource(R.drawable.background_off_repeat);
mVideo.setVisibility(View.VISIBLE);
mVideo.setOnPreparedListener(this);
mVideo.setOnCompletionListener(this);
mVideo.setDrawingCacheEnabled(true);
}
private void stopPeaceOfMindVideo()
{
mVideo.removeCallbacks(null);
if(mVideo.getVisibility() != View.VISIBLE){
mVideo.setVisibility(View.VISIBLE);
}
Animation fadeOut = AnimationUtils.loadAnimation(this, R.anim.fade_out_fast);
mVideo.startAnimation(fadeOut);
updateBackground(false);
fadeOut.setAnimationListener(new AnimationListener()
{
@Override
public void onAnimationStart(Animation animation)
{
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
@Override
public void onAnimationEnd(Animation animation)
{
mVideo.setVisibility(View.INVISIBLE);
mVideo.stopPlayback();
}
});
}
public void onPrepared(MediaPlayer mp)
{
//Used to avoid the initial black flicker
//remove the foreground 30 miliseconds after the video starts
mVideo.postDelayed(new Runnable() {
public void run()
{
if (mVideo.isPlaying()){
mVideo.setBackgroundResource(0);
return;
}
}
}, 30);
//Used to avoid the final black flicker
//remove the foreground 20 miliseconds before the video ends
mVideo.postDelayed(new Runnable() {
public void run()
{
if (mVideo.isPlaying()){
mVideo.setBackgroundResource(R.drawable.background_on_repeat);
return;
}
}
}, mVideo.getDuration() - 20);
mVideo.start();
}
@Override
public void onCompletion(MediaPlayer mp) {
updateBackground(true);
mVideo.removeCallbacks(null);
mVideo.setBackgroundResource(R.drawable.background_on_repeat);
mVideo.stopPlayback();
}
@Override
public synchronized void peaceOfMindEnded()
{
try {
mSemaphore.tryAcquire(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
Animation fadeIn = AnimationUtils.loadAnimation(this, R.anim.fade_in_fast);
mSeekbarBackgroundOff.startAnimation(fadeIn);
Animation fadeOut = AnimationUtils.loadAnimation(this, R.anim.fade_out_fast);
mSeekbarBackgroundOn.startAnimation(fadeOut);
fadeIn.setAnimationListener(new AnimationListener()
{
@Override
public void onAnimationStart(Animation animation)
{
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
@Override
public void onAnimationEnd(Animation animation)
{
mSeekbarBackgroundOff.setVisibility(View.VISIBLE);
mSeekbarBackgroundOn.setVisibility(View.GONE);
updateScreenTexts();
stopPeaceOfMindVideo();
mSemaphore.release();
}
});
updateScreenTexts();
mVerticalSeekBar.setThumb(getResources().getDrawable(R.drawable.seekbar_thumb_off));
mVerticalSeekBar.setThumbOffset(0);
mVerticalSeekBar.setInvertedProgress(0);
updateTextForNewTime(0, 0);
updateTimeTextLabel(0);
mTotalTimeText.setVisibility(View.INVISIBLE);
}
@Override
public void peaceOfMindUpdated(long pastTime, long newTargetTime)
{
updateTextForNewTime(pastTime, newTargetTime);
}
@Override
protected void onStart() {
super.onStart();
FlurryHelper.startFlurrySession(this);
}
@Override
protected void onStop() {
super.onStop();
FlurryHelper.endFlurrySession(this);
}
}