/*
* Copyright (C) 2016 Google Inc. All Rights Reserved.
*
* 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 com.google.android.apps.santatracker.dasherdancer;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.Log;
import android.util.LruCache;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.widget.ImageView;
import com.google.android.apps.santatracker.games.PlayGamesFragment;
import com.google.android.apps.santatracker.games.SignInListener;
import com.google.android.apps.santatracker.util.AnalyticsManager;
import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
import com.google.android.apps.santatracker.util.MeasurementManager;
import com.google.android.gms.games.Games;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.squareup.seismic.ShakeDetector;
import com.squareup.seismic.ShakeDetector.Listener;
import java.util.HashSet;
public class DasherDancerActivity extends FragmentActivity implements
OnGestureListener, OnScaleGestureListener, Handler.Callback, Listener, SensorEventListener,
AnimatorListener, OnPageChangeListener, SignInListener {
private static final String TAG = "DasherDancer";
/**
* Extra key used to pass back the character id that should be selected, set by the CharacterActivity.
*/
public static final String EXTRA_CHARACTER_ID = "extra_character_id";
//Character ids, which are also indices in the sCharacters array.
public static final int CHARACTER_ID_SANTA = 0;
public static final int CHARACTER_ID_ELF = 1;
public static final int CHARACTER_ID_REINDEER = 2;
public static final int CHARACTER_ID_SNOWMAN = 3;
/** Number of times to try downsampling before giving up **/
public static final int MAX_DOWNSAMPLING_ATTEMPTS = 3;
/**
* Request code for calling CharacterActivity for result.
*/
private static final int sCharacterRequestCode = 1;
/**
* Our array of playable characters. Add more characters here an create new CHARACTER_ID_* static variables.
*/
private static final Character[] sCharacters = new Character[]{
new Santa(), new Elf(), new Reindeer(), new Snowman()
};
private boolean[] mCharacterInitialized = new boolean[] {
false, false, false, false
};
private int[][] mSoundIds = new int[][]{
{-1,-1,-1,-1,-1,-1,-1,-1,-1}, //santa
{-1,-1,-1,-1,-1,-1,-1,-1,-1}, //elf
{-1,-1,-1,-1,-1,-1,-1,-1,-1}, //reindeer
{-1,-1,-1,-1,-1,-1,-1,-1,-1} //snowman
};
private LruCache<Integer, Drawable> mMemoryCache;
private NoSwipeViewPager mPager;
private Handler mHandler;
private ShakeDetector mDetector;
private LoadBitmapsTask mLoadBitmapsTask;
private LoadCharacterResourcesTask mLoadCharacterTask;
private ObjectAnimator mAnimator;
private boolean mPlayingRest = false;
private boolean mAnimCanceled = false;
private boolean mAnimPlaying = false;
private boolean mScaling = false;
private boolean mInitialized = false;
private SoundPool mSoundPool;
private int mSoundId = -1;
private boolean mCanTouch = false;
private ObjectAnimator mProgressAnimator;
private ActivityManager mActivityManager;
private FirebaseAnalytics mMeasurement;
// Bitmap downsampling options
private BitmapFactory.Options mOptions;
private int mDownSamplingAttempts;
private PlayGamesFragment mGamesFragment;
// For achievements
private HashSet<Integer>[] mAchievements;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dasher_dancer);
mMeasurement = FirebaseAnalytics.getInstance(this);
MeasurementManager.recordScreenView(mMeasurement,
getString(R.string.analytics_screen_dasher));
AnalyticsManager.initializeAnalyticsTracker(this);
AnalyticsManager.sendScreenView(R.string.analytics_screen_dasher);
mActivityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
mMemoryCache = new LruCache<Integer, Drawable>(240) {
protected void entryRemoved(boolean evicted, Integer key, Drawable oldValue, Drawable newValue) {
if ((oldValue != null) && (oldValue != newValue)) {
if (oldValue instanceof InsetDrawableCompat){
Drawable drawable = ((InsetDrawableCompat) oldValue).getDrawable();
if (drawable instanceof BitmapDrawable){
((BitmapDrawable) drawable).getBitmap().recycle();
}
}
}
}
};
// Initialize default Bitmap options
mOptions = new BitmapFactory.Options();
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
mOptions.inSampleSize = getResources().getInteger(R.integer.res);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (mActivityManager.isLowRamDevice()) {
Log.w(TAG, "isLowRamDevice: downsampling default bitmap options");
mOptions.inSampleSize *= 2;
}
}
CharacterAdapter adapter = new CharacterAdapter(sCharacters);
mPager = (NoSwipeViewPager) findViewById(R.id.character_pager);
mPager.setAdapter(adapter);
mPager.setGestureDetectorListeners(this, this, this);
mPager.setOnPageChangeListener(this);
mHandler = new Handler(getMainLooper(), this);
mDetector = new ShakeDetector(this);
mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
mAchievements = new HashSet[4];
mAchievements[0] = new HashSet<Integer>();
mAchievements[1] = new HashSet<Integer>();
mAchievements[2] = new HashSet<Integer>();
mAchievements[3] = new HashSet<Integer>();
mProgressAnimator = ObjectAnimator.ofFloat(findViewById(R.id.progress),"rotation",360f);
mProgressAnimator.setDuration(4000);
mProgressAnimator.start();
mGamesFragment = PlayGamesFragment.getInstance(this, this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
ImmersiveModeHelper.setImmersiveSticky(getWindow());
ImmersiveModeHelper.installSystemUiVisibilityChangeListener(getWindow());
}
}
@Override
public void onResume() {
super.onResume();
SensorManager manager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
Sensor accel = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
manager.registerListener(this, accel, SensorManager.SENSOR_DELAY_NORMAL);
mDetector.start(manager);
if(mInitialized) {
//Start the animation for the first character.
mPager.postDelayed(new Runnable() {
@Override
public void run() {
loadAnimation(true,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_IDLE),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_IDLE),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_IDLE));
}
}, 300);
}
else {
if(mLoadCharacterTask != null) {
mLoadCharacterTask.cancel(true);
}
mLoadCharacterTask = new LoadCharacterResourcesTask(mPager.getCurrentItem());
mLoadCharacterTask.execute();
}
}
@Override
public void onPause() {
super.onPause();
mDetector.stop();
SensorManager manager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
manager.unregisterListener(this);;
if(mAnimator != null) {
mAnimator.cancel();
}
FrameAnimationView character = (FrameAnimationView) mPager.findViewWithTag(mPager.getCurrentItem());
if (character != null) {
character.setImageDrawable(null);
}
}
/**
* Finishes the activity.
* @param view
*/
public void onNavClick(View view) {
if(mLoadBitmapsTask != null) {
mLoadBitmapsTask.cancel(true);
}
if(mAnimator != null) {
mAnimator.cancel();
}
finish();
}
/**
* Starts the CharacterActivity for result. That result is an integer that corresponds to
* the index of an entry in sCharacters.
* @param view
*/
public void onChangeClick(View view) {
if(mLoadBitmapsTask != null) {
mLoadBitmapsTask.cancel(true);
}
if(mLoadCharacterTask != null) {
mLoadCharacterTask.cancel(true);
}
if(mAnimator != null) {
mAnimator.cancel();
}
FrameAnimationView character = (FrameAnimationView) mPager.findViewWithTag(mPager.getCurrentItem());
character.setImageDrawable(null);
character.setFrames(null, null);
character.invalidate();
Intent intent = new Intent(this, CharacterActivity.class);
startActivityForResult(intent, sCharacterRequestCode);
}
/**
* Moves the view pager to the next character to the left of the current position.
*/
public void onLeftClick(View view) {
final int currentPosition = mPager.getCurrentItem();
if(currentPosition != 0) {
characterSelectedHelper(currentPosition - 1, true);
}
}
/**
* Moves the view pager to the next character to the right of the current position.
*/
public void onRightClick(View view) {
final int currentPosition = mPager.getCurrentItem();
if(currentPosition != mPager.getAdapter().getCount()-1) {
characterSelectedHelper(currentPosition + 1, true);
}
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onShowPress(MotionEvent e) {
//Ignore this.
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if(!mAnimPlaying) {
mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_TAP];
}
AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
sCharacters[mPager.getCurrentItem()].getCharacterName(),
getString(R.string.analytics_action_tap));
updateGestureAchievements(Character.ANIM_TAP);
loadAnimation(false,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_TAP),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_TAP),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_TAP));
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
//Ignore
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
float xDelta = Math.abs(e1.getX() - e2.getX());
float yDelta = Math.abs(e1.getY() - e2.getY());
if(xDelta > yDelta) {
//Moving side to side.
if(e1.getX() > e2.getX()) {
//Moving left.
if(!mAnimPlaying) {
mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SWIPE_LEFT];
}
AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
sCharacters[mPager.getCurrentItem()].getCharacterName(),
getString(R.string.analytics_action_swipe_left));
updateGestureAchievements(Character.ANIM_SWIPE_LEFT);
loadAnimation(false,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SWIPE_LEFT),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SWIPE_LEFT),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SWIPE_LEFT));
}
else if(e2.getX() > e1.getX()) {
//Moving right.
if(!mAnimPlaying) {
mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SWIPE_RIGHT];
}
AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
sCharacters[mPager.getCurrentItem()].getCharacterName(),
getString(R.string.analytics_action_swipe_right));
updateGestureAchievements(Character.ANIM_SWIPE_RIGHT);
loadAnimation(false,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SWIPE_RIGHT),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SWIPE_RIGHT),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SWIPE_RIGHT));
}
}
else {
//We are moving up and down
if(e1.getY() > e2.getY()) {
//Moving up.
if(!mAnimPlaying) {
mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SWIPE_UP];
}
AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
sCharacters[mPager.getCurrentItem()].getCharacterName(),
getString(R.string.analytics_action_swipe_up));
updateGestureAchievements(Character.ANIM_SWIPE_UP);
loadAnimation(false,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SWIPE_UP),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SWIPE_UP),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SWIPE_UP));
}
else if(e2.getY() > e1.getY()) {
//Moving down.
if(!mAnimPlaying) {
mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SWIPE_DOWN];
}
AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
sCharacters[mPager.getCurrentItem()].getCharacterName(),
getString(R.string.analytics_action_swipe_down));
updateGestureAchievements(Character.ANIM_SWIPE_DOWN);
loadAnimation(false,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SWIPE_DOWN),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SWIPE_DOWN),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SWIPE_DOWN));
}
}
return false;
}
@Override
public boolean handleMessage(Message msg) {
loadAnimation(true,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_IDLE),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_IDLE),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_IDLE));
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
return false;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mScaling = true;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
mScaling = false;
if(detector.getScaleFactor() > 1) {
//Pinch in
if(!mAnimPlaying) {
mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_PINCH_IN];
}
AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
sCharacters[mPager.getCurrentItem()].getCharacterName(),
getString(R.string.analytics_action_pinch_in));
updateGestureAchievements(Character.ANIM_PINCH_IN);
loadAnimation(false,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_PINCH_IN),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_PINCH_IN),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_PINCH_IN));
}
else if(detector.getScaleFactor() < 1) {
//Pinch out
if(!mAnimPlaying) {
mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_PINCH_OUT];
}
AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
sCharacters[mPager.getCurrentItem()].getCharacterName(),
getString(R.string.analytics_action_pinch_out));
updateGestureAchievements(Character.ANIM_PINCH_OUT);
loadAnimation(false,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_PINCH_OUT),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_PINCH_OUT),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_PINCH_OUT));
}
}
@Override
public void onSensorChanged(SensorEvent event) {
//Ignore this.
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
//Ignore this.
}
@Override
public void hearShake() {
if(!mAnimPlaying) {
mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SHAKE];
}
AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
sCharacters[mPager.getCurrentItem()].getCharacterName(),
getString(R.string.analytics_action_shake));
updateGestureAchievements(Character.ANIM_SHAKE);
loadAnimation(false,
sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SHAKE),
sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SHAKE),
sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SHAKE));
}
/**
* Helper method to load and start animations. Takes care of canceling any ongoing animations,
* and will return without executing anything if mAnimPlaying is true or mScaling is true.
* @param playingRest
* @param animationTime
* @param frameIndices
* @param frameResourceIds
*/
private void loadAnimation(boolean playingRest, long animationTime, int[] frameIndices, int[] frameResourceIds) {
if((!playingRest && (mAnimPlaying || mScaling)) || !mCanTouch) {
return;
}
if(playingRest) {
mAnimPlaying = false;
}
else {
mAnimPlaying = true;
}
mPlayingRest = playingRest;
if(mLoadBitmapsTask != null) {
mLoadBitmapsTask.cancel(true);
mAnimator.cancel();
}
mLoadBitmapsTask = new LoadBitmapsTask(animationTime, frameIndices, frameResourceIds);
mLoadBitmapsTask.execute();
}
/**
* Load and cache all sounds for a given character.
* @param characterIndex index of the character in the array, like {@link #CHARACTER_ID_SANTA}.
*/
private void loadSoundsForCharacter(int characterIndex) {
for (int animationId : Character.ALL_ANIMS) {
// No need to load sounds twice
if (mSoundIds[characterIndex][animationId] != -1) {
continue;
}
int soundResource = sCharacters[characterIndex].getSoundResource(animationId);
if (soundResource != -1) {
mSoundIds[characterIndex][animationId] =
mSoundPool.load(this, soundResource, 1);
}
}
}
@Override
public void onSignInFailed() {
}
@Override
public void onSignInSucceeded() {
}
@Nullable
private Drawable tryLoadBitmap(@DrawableRes int resourceId) throws BitmapLoadException {
try {
Bitmap bmp = BitmapFactory.decodeResource(getResources(), resourceId, mOptions);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bmp);
Point p = ResourceOffsets.getOffsets(resourceId);
int x = Math.round(p.x / (float)mOptions.inSampleSize);
int y = Math.round(p.y / (float)mOptions.inSampleSize);
int w = Math.round(ResourceOffsets.ORIG_SIZE.x / (float)mOptions.inSampleSize);
int h = Math.round(ResourceOffsets.ORIG_SIZE.y / (float)mOptions.inSampleSize);
InsetDrawableCompat insetDrawable = new InsetDrawableCompat(bitmapDrawable,
x,
y,
w - bmp.getWidth() - x,
h - bmp.getHeight() - y
);
return insetDrawable;
} catch (OutOfMemoryError oom) {
Log.w(TAG, "Out of memory error, inSampleSize=" + mOptions.inSampleSize);
if (mDownSamplingAttempts < MAX_DOWNSAMPLING_ATTEMPTS) {
mOptions.inSampleSize *= 2;
mDownSamplingAttempts++;
}
}
throw new BitmapLoadException("Failed to load resource ID: " + resourceId);
}
/**
* Load all of the resources for a given Character, then begin playing the "IDLE" animation.
* for that character.
*/
private class LoadCharacterResourcesTask extends RetryableAsyncTask<Void, Void, Void> {
private Character mCharacter;
private int mCharacterIndex;
LoadCharacterResourcesTask(int characterIndex) {
mCharacter = sCharacters[characterIndex];
mCharacterIndex = characterIndex;
}
@Override
protected Void doInBackground(Void... params) {
mCanTouch = false;
//See if we can free up any memory before we allocate some ourselves.
//Request garbage collection.
System.gc();
// Load all sounds for this character
loadSoundsForCharacter(mCharacterIndex);
// Load all animations types for this character
for (int animation : Character.ALL_ANIMS) {
for (int resourceId : mCharacter.getFrames(animation)) {
if (isCancelled()) {
break;
}
if (mMemoryCache.get(resourceId) == null) {
try {
Drawable bitmap = tryLoadBitmap(resourceId);
mMemoryCache.put(resourceId, bitmap);
} catch (BitmapLoadException e) {
Log.e(TAG, "LoadCharacterResourcesTask: failed", e);
// Retry the task
return retrySelf(params);
}
if (isCancelled()) {
// Remove the BMP we just added
// The check and remove should be atomic so we synchronize
// (There could be an evict going on so make sure it's still there...
synchronized(mMemoryCache) {
if (mMemoryCache.get(resourceId) != null) {
mMemoryCache.remove(resourceId);
}
}
}
}
}
}
return null;
}
@Override
public void onPostExecute(Void result) {
if(isCancelled()) {
return;
}
findViewById(R.id.progress).setVisibility(View.GONE);
Character currentCharacter = sCharacters[mPager.getCurrentItem()];
Drawable[] frames = new Drawable[currentCharacter.getFrames(Character.ANIM_IDLE).length];
for(int i=0; i<frames.length; i++) {
frames[i] = mMemoryCache.get(currentCharacter.getFrames(Character.ANIM_IDLE)[i]);
}
FrameAnimationView characterView =
(FrameAnimationView) mPager.findViewWithTag(mPager.getCurrentItem());
characterView.setFrames(frames, currentCharacter.getFrameIndices(Character.ANIM_IDLE));
mPlayingRest = true;
mAnimator = ObjectAnimator.ofInt(characterView, "frameIndex", 0,
currentCharacter.getFrameIndices(Character.ANIM_IDLE).length-1);
mAnimator.setDuration(currentCharacter.getDuration(Character.ANIM_IDLE));
mAnimator.addListener(DasherDancerActivity.this);
mAnimator.start();
mInitialized = true;
mCanTouch = true;
}
@Override
public boolean shouldRetry() {
return mDownSamplingAttempts < MAX_DOWNSAMPLING_ATTEMPTS;
}
@Override
public void onPrepareForRetry() {
// Clear all frames for this character
for (int animation : Character.ALL_ANIMS) {
for (int resourceId : mCharacter.getFrames(animation)) {
mMemoryCache.remove(resourceId);
}
}
// Try to retry
System.gc();
}
}
/**
* AsyncTask that loads bitmaps for animation and starts the animation upon completion.
*/
private class LoadBitmapsTask extends RetryableAsyncTask<Void, Void, Drawable[]> {
private int[] mFrames;
private int[] mFrameIndices;
private long mDuration;
public LoadBitmapsTask(long duration, int[] frameIndices, int[] frames) {
mDuration = duration;
mFrameIndices = frameIndices;
mFrames = frames;
}
@Override
protected Drawable[] doInBackground(Void... params) {
Drawable[] bitmaps = new Drawable[mFrames.length];
for(int i = 0; i < mFrames.length; i++) {
if (isCancelled()) {
break;
}
int id = mFrames[i];
if(mMemoryCache.get(id) == null) {
try {
bitmaps[i] = tryLoadBitmap(id);
} catch (BitmapLoadException e) {
Log.e(TAG, "LoadBitmapsTask: failed", e);
return retrySelf(params);
}
mMemoryCache.put(id, bitmaps[i]);
if (isCancelled()) {
synchronized (mMemoryCache) {
if (mMemoryCache.get(id) != null) {
mMemoryCache.remove(id);
}
}
}
}
else {
bitmaps[i] = mMemoryCache.get(mFrames[i]);
}
}
return bitmaps;
}
@Override
public void onPostExecute(Drawable[] result) {
if(result == null || isCancelled()) {
return;
}
FrameAnimationView character = (FrameAnimationView) mPager.findViewWithTag(mPager.getCurrentItem());
character.setFrames(result, mFrameIndices);
if(mAnimator != null) {
mAnimator.cancel();
}
mAnimator = ObjectAnimator.ofInt(character, "frameIndex", 0, mFrameIndices.length-1);
mAnimator.setDuration(mDuration);
mAnimator.addListener(DasherDancerActivity.this);
mAnimator.start();
if(mSoundId != -1) {
mSoundPool.play(mSoundId,1f,1f,0,0,1);
mSoundId = -1;
}
}
@Override
public boolean shouldRetry() {
return (mDownSamplingAttempts < MAX_DOWNSAMPLING_ATTEMPTS);
}
@Override
public void onPrepareForRetry() {
// Remove all frames this task should load
for (int id : mFrames) {
mMemoryCache.remove(id);
}
// See if we can now GC
System.gc();
}
}
@Override
public void onAnimationStart(Animator animation) {
mAnimCanceled = false;
if(!mPlayingRest) {
mAnimPlaying = true;
}
}
@Override
public void onAnimationEnd(Animator animation) {
if(mAnimCanceled) {
return;
}
mAnimPlaying = false; //yoda
if(mPlayingRest) {
//We are at rest, so play the idle animation again.
FrameAnimationView character = (FrameAnimationView) mPager.findViewWithTag(mPager.getCurrentItem());
mAnimator = ObjectAnimator.ofInt(
character,
"frameIndex",
0, sCharacters[0].getFrameIndices(Character.ANIM_IDLE).length);
mAnimator.setDuration(sCharacters[0].getDuration(Character.ANIM_IDLE));
mAnimator.addListener(DasherDancerActivity.this);
mAnimator.start();
}
else {
//We finished an animation triggered by a gesture, so start the idle animation again.
mHandler.sendEmptyMessage(1);
}
}
@Override
public void onAnimationCancel(Animator animation) {
mAnimCanceled = true;
mAnimPlaying = false;
}
@Override
public void onAnimationRepeat(Animator animation) {
//Ignore
}
@Override
public void onPageScrollStateChanged(int arg0) {
//Ignore
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
//Ignore
}
@Override
public void onPageSelected(int arg0) {
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == sCharacterRequestCode) {
if (resultCode == RESULT_OK) {
mInitialized = false;
if (mPager != null) {
FrameAnimationView character = (FrameAnimationView) mPager.findViewWithTag(mPager.getCurrentItem());
character.setImageDrawable(null);
}
if (data.getExtras() != null) {
//Based on the character id returned, move the view pager to that character.
final int position = data.getExtras().getInt(EXTRA_CHARACTER_ID);
characterSelectedHelper(position, false);
}
}
}
}
@Override
public void onBackPressed() {
//If we are backing out of the game, clear the cache to free memory.
mSoundPool.release();
mMemoryCache.evictAll();
//Request garbage collection.
System.gc();
super.onBackPressed();
}
@Override
public void onDestroy() {
mSoundPool.release();
mMemoryCache.evictAll();
//Request garbage collection.
System.gc();
super.onDestroy();
}
private void characterSelectedHelper(final int position, final boolean smoothScroll) {
if(mLoadBitmapsTask != null) {
mLoadBitmapsTask.cancel(true);
}
if(mLoadCharacterTask != null) {
mLoadCharacterTask.cancel(true);
}
if(mAnimator != null) {
mAnimator.cancel();
}
if(position == 0) {
findViewById(R.id.left_button).setVisibility(View.GONE);
findViewById(R.id.right_button).setVisibility(View.VISIBLE);
}
else if(position+1 == mPager.getAdapter().getCount()) {
findViewById(R.id.right_button).setVisibility(View.GONE);
findViewById(R.id.left_button).setVisibility(View.VISIBLE);
}
else {
findViewById(R.id.left_button).setVisibility(View.VISIBLE);
findViewById(R.id.right_button).setVisibility(View.VISIBLE);
}
AnalyticsManager.sendEvent(R.string.analytics_category_character,
R.string.analytics_action_character_change,
sCharacters[position].getCharacterName());
mPager.postDelayed(new Runnable() {
@Override
public void run() {
//Show progress
mPager.setCurrentItem(position, smoothScroll);
findViewById(R.id.progress).setVisibility(View.VISIBLE);
mProgressAnimator.start();
((ImageView) mPager.findViewWithTag(mPager.getCurrentItem())).setImageDrawable(null);
mMemoryCache.evictAll();
//Request garbage collection.
System.gc();
if (mLoadCharacterTask != null) {
mLoadCharacterTask.cancel(true);
}
mLoadCharacterTask = new LoadCharacterResourcesTask(position);
mLoadCharacterTask.execute();
}
}, 100);
}
private void updateGestureAchievements(int type) {
int character = mPager.getCurrentItem();
mAchievements[character].add(type);
if (mAchievements[character].size() == 8) {
if (mGamesFragment.isSignedIn()) {
if (character == CHARACTER_ID_SANTA) {
Games.Achievements.unlock(mGamesFragment.getGamesApiClient(), getString(R.string.achievement_santas_dance_party));
MeasurementManager.recordAchievement(mMeasurement,
getString(R.string.achievement_santas_dance_party),
getString(R.string.analytics_screen_dasher));
} else if (character == CHARACTER_ID_ELF) {
Games.Achievements.unlock(mGamesFragment.getGamesApiClient(), getString(R.string.achievement_elfs_dance_party));
MeasurementManager.recordAchievement(mMeasurement,
getString(R.string.achievement_elfs_dance_party),
getString(R.string.analytics_screen_dasher));
} else if (character == CHARACTER_ID_REINDEER) {
Games.Achievements.unlock(mGamesFragment.getGamesApiClient(), getString(R.string.achievement_rudolphs_dance_party));
MeasurementManager.recordAchievement(mMeasurement,
getString(R.string.achievement_rudolphs_dance_party),
getString(R.string.analytics_screen_dasher));
} else if (character == CHARACTER_ID_SNOWMAN) {
Games.Achievements.unlock(mGamesFragment.getGamesApiClient(), getString(R.string.achievement_snowmans_dance_party));
MeasurementManager.recordAchievement(mMeasurement,
getString(R.string.achievement_snowmans_dance_party),
getString(R.string.analytics_screen_dasher));
}
}
}
}
/**
* Convenience class for exception when loading Bitmaps.
*/
private static class BitmapLoadException extends Exception {
public BitmapLoadException(String msg) {
super(msg);
}
}
}